@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.
Files changed (330) hide show
  1. package/dist/auth/config.d.ts +20 -0
  2. package/dist/auth/config.d.ts.map +1 -0
  3. package/dist/auth/config.js +114 -0
  4. package/dist/auth/config.js.map +1 -0
  5. package/dist/auth/index.d.ts +5 -0
  6. package/dist/auth/index.d.ts.map +1 -0
  7. package/dist/auth/index.js +17 -0
  8. package/dist/auth/index.js.map +1 -0
  9. package/dist/auth/middleware.d.ts +45 -0
  10. package/dist/auth/middleware.d.ts.map +1 -0
  11. package/dist/auth/middleware.js +170 -0
  12. package/dist/auth/middleware.js.map +1 -0
  13. package/dist/auth/service.d.ts +80 -0
  14. package/dist/auth/service.d.ts.map +1 -0
  15. package/dist/auth/service.js +298 -0
  16. package/dist/auth/service.js.map +1 -0
  17. package/dist/cli/index.d.ts +6 -0
  18. package/dist/cli/index.d.ts.map +1 -0
  19. package/dist/cli/index.js +375 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/mock-server.d.ts +20 -0
  22. package/dist/cli/mock-server.d.ts.map +1 -0
  23. package/dist/cli/mock-server.js +261 -0
  24. package/dist/cli/mock-server.js.map +1 -0
  25. package/dist/compliance/compliance-generator.d.ts +34 -0
  26. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  27. package/dist/compliance/compliance-generator.js +320 -0
  28. package/dist/compliance/compliance-generator.js.map +1 -0
  29. package/dist/compliance/index.d.ts +8 -0
  30. package/dist/compliance/index.d.ts.map +1 -0
  31. package/dist/compliance/index.js +17 -0
  32. package/dist/compliance/index.js.map +1 -0
  33. package/dist/compliance/templates.d.ts +34 -0
  34. package/dist/compliance/templates.d.ts.map +1 -0
  35. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  36. package/dist/compliance/templates.js.map +1 -0
  37. package/dist/compliance/types.d.ts +64 -0
  38. package/dist/compliance/types.d.ts.map +1 -0
  39. package/dist/compliance/types.js +64 -0
  40. package/dist/compliance/types.js.map +1 -0
  41. package/dist/db/index.d.ts +17 -0
  42. package/dist/db/index.d.ts.map +1 -0
  43. package/dist/db/index.js +80 -0
  44. package/dist/db/index.js.map +1 -0
  45. package/dist/db/schema.d.ts +3886 -0
  46. package/dist/db/schema.d.ts.map +1 -0
  47. package/dist/db/schema.js +425 -0
  48. package/dist/db/schema.js.map +1 -0
  49. package/dist/db/utils.d.ts +252 -0
  50. package/dist/db/utils.d.ts.map +1 -0
  51. package/dist/db/utils.js +295 -0
  52. package/dist/db/utils.js.map +1 -0
  53. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  54. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  55. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +856 -726
  56. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  57. package/dist/feed-analyzer/index.d.ts +8 -0
  58. package/dist/feed-analyzer/index.d.ts.map +1 -0
  59. package/dist/feed-analyzer/index.js +19 -0
  60. package/dist/feed-analyzer/index.js.map +1 -0
  61. package/dist/feed-analyzer/types.d.ts +285 -0
  62. package/dist/feed-analyzer/types.d.ts.map +1 -0
  63. package/dist/feed-analyzer/types.js +175 -0
  64. package/dist/feed-analyzer/types.js.map +1 -0
  65. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  66. package/dist/generator/index.d.ts.map +1 -0
  67. package/dist/generator/index.js +13 -0
  68. package/dist/generator/index.js.map +1 -0
  69. package/dist/generator/key-generator.d.ts +24 -0
  70. package/dist/generator/key-generator.d.ts.map +1 -0
  71. package/dist/generator/key-generator.js +144 -0
  72. package/dist/generator/key-generator.js.map +1 -0
  73. package/dist/generator/profile-builder.d.ts +15 -0
  74. package/dist/generator/profile-builder.d.ts.map +1 -0
  75. package/dist/generator/profile-builder.js +338 -0
  76. package/dist/generator/profile-builder.js.map +1 -0
  77. package/dist/hosting/artifacts-generator.d.ts +10 -0
  78. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  79. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  80. package/dist/hosting/artifacts-generator.js.map +1 -0
  81. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  82. package/dist/hosting/index.d.ts.map +1 -0
  83. package/dist/hosting/index.js +10 -0
  84. package/dist/hosting/index.js.map +1 -0
  85. package/dist/index.d.ts +18 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +78 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/lib/analytics.d.ts +337 -0
  90. package/dist/lib/analytics.d.ts.map +1 -0
  91. package/dist/lib/analytics.js +188 -0
  92. package/dist/lib/analytics.js.map +1 -0
  93. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  94. package/dist/security/index.d.ts.map +1 -0
  95. package/dist/security/index.js +12 -0
  96. package/dist/security/index.js.map +1 -0
  97. package/dist/security/security-scanner.d.ts +10 -0
  98. package/dist/security/security-scanner.d.ts.map +1 -0
  99. package/dist/security/security-scanner.js +669 -0
  100. package/dist/security/security-scanner.js.map +1 -0
  101. package/dist/security/types.d.ts +80 -0
  102. package/dist/security/types.d.ts.map +1 -0
  103. package/dist/security/types.js +21 -0
  104. package/dist/security/types.js.map +1 -0
  105. package/dist/services/analytics.d.ts +114 -0
  106. package/dist/services/analytics.d.ts.map +1 -0
  107. package/dist/services/analytics.js +862 -0
  108. package/dist/services/analytics.js.map +1 -0
  109. package/dist/services/badge.d.ts +31 -0
  110. package/dist/services/badge.d.ts.map +1 -0
  111. package/dist/services/badge.js +152 -0
  112. package/dist/services/badge.js.map +1 -0
  113. package/dist/services/cron.d.ts +125 -0
  114. package/dist/services/cron.d.ts.map +1 -0
  115. package/dist/services/cron.js +613 -0
  116. package/dist/services/cron.js.map +1 -0
  117. package/dist/services/directory.d.ts +106 -0
  118. package/dist/services/directory.d.ts.map +1 -0
  119. package/dist/services/directory.js +351 -0
  120. package/dist/services/directory.js.map +1 -0
  121. package/dist/services/email.d.ts +112 -0
  122. package/dist/services/email.d.ts.map +1 -0
  123. package/dist/services/email.js +772 -0
  124. package/dist/services/email.js.map +1 -0
  125. package/dist/services/hosted-profiles.d.ts +77 -0
  126. package/dist/services/hosted-profiles.d.ts.map +1 -0
  127. package/dist/services/hosted-profiles.js +433 -0
  128. package/dist/services/hosted-profiles.js.map +1 -0
  129. package/dist/services/latency.d.ts +67 -0
  130. package/dist/services/latency.d.ts.map +1 -0
  131. package/dist/services/latency.js +274 -0
  132. package/dist/services/latency.js.map +1 -0
  133. package/dist/services/manifest-compliance.d.ts +64 -0
  134. package/dist/services/manifest-compliance.d.ts.map +1 -0
  135. package/dist/services/manifest-compliance.js +271 -0
  136. package/dist/services/manifest-compliance.js.map +1 -0
  137. package/dist/services/monitoring-diff.d.ts +31 -0
  138. package/dist/services/monitoring-diff.d.ts.map +1 -0
  139. package/dist/services/monitoring-diff.js +189 -0
  140. package/dist/services/monitoring-diff.js.map +1 -0
  141. package/dist/services/notifications.d.ts +46 -0
  142. package/dist/services/notifications.d.ts.map +1 -0
  143. package/dist/services/notifications.js +88 -0
  144. package/dist/services/notifications.js.map +1 -0
  145. package/dist/services/stripe.d.ts +93 -0
  146. package/dist/services/stripe.d.ts.map +1 -0
  147. package/dist/services/stripe.js +490 -0
  148. package/dist/services/stripe.js.map +1 -0
  149. package/dist/services/validation-history.d.ts +99 -0
  150. package/dist/services/validation-history.d.ts.map +1 -0
  151. package/dist/services/validation-history.js +344 -0
  152. package/dist/services/validation-history.js.map +1 -0
  153. package/dist/services/validation-logging.d.ts +103 -0
  154. package/dist/services/validation-logging.d.ts.map +1 -0
  155. package/dist/services/validation-logging.js +210 -0
  156. package/dist/services/validation-logging.js.map +1 -0
  157. package/dist/services/validation.d.ts +119 -0
  158. package/dist/services/validation.d.ts.map +1 -0
  159. package/dist/services/validation.js +1185 -0
  160. package/dist/services/validation.js.map +1 -0
  161. package/dist/simulator/agent-simulator.d.ts +69 -0
  162. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  163. package/dist/simulator/agent-simulator.js +870 -0
  164. package/dist/simulator/agent-simulator.js.map +1 -0
  165. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  166. package/dist/simulator/index.d.ts.map +1 -0
  167. package/dist/simulator/index.js +23 -0
  168. package/dist/simulator/index.js.map +1 -0
  169. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +171 -170
  170. package/dist/simulator/types.d.ts.map +1 -0
  171. package/dist/simulator/types.js +18 -0
  172. package/dist/simulator/types.js.map +1 -0
  173. package/dist/types/acp-validation.d.ts +87 -0
  174. package/dist/types/acp-validation.d.ts.map +1 -0
  175. package/dist/types/acp-validation.js +40 -0
  176. package/dist/types/acp-validation.js.map +1 -0
  177. package/dist/types/analytics.d.ts +182 -0
  178. package/dist/types/analytics.d.ts.map +1 -0
  179. package/dist/types/analytics.js +7 -0
  180. package/dist/types/analytics.js.map +1 -0
  181. package/dist/types/generator.d.ts +106 -0
  182. package/dist/types/generator.d.ts.map +1 -0
  183. package/dist/types/generator.js +6 -0
  184. package/dist/types/generator.js.map +1 -0
  185. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  186. package/dist/types/index.d.ts.map +1 -0
  187. package/dist/types/index.js +23 -0
  188. package/dist/types/index.js.map +1 -0
  189. package/dist/types/ucp-profile.d.ts +111 -0
  190. package/dist/types/ucp-profile.d.ts.map +1 -0
  191. package/dist/types/ucp-profile.js +45 -0
  192. package/dist/types/ucp-profile.js.map +1 -0
  193. package/dist/types/validation.d.ts +76 -0
  194. package/dist/types/validation.d.ts.map +1 -0
  195. package/dist/types/validation.js +42 -0
  196. package/dist/types/validation.js.map +1 -0
  197. package/dist/validator/acp/index.d.ts +31 -0
  198. package/dist/validator/acp/index.d.ts.map +1 -0
  199. package/dist/validator/acp/index.js +574 -0
  200. package/dist/validator/acp/index.js.map +1 -0
  201. package/dist/validator/index.d.ts +26 -0
  202. package/dist/validator/index.d.ts.map +1 -0
  203. package/dist/validator/index.js +161 -0
  204. package/dist/validator/index.js.map +1 -0
  205. package/dist/validator/network-validator.d.ts +28 -0
  206. package/dist/validator/network-validator.d.ts.map +1 -0
  207. package/dist/validator/network-validator.js +319 -0
  208. package/dist/validator/network-validator.js.map +1 -0
  209. package/dist/validator/rules-validator.d.ts +19 -0
  210. package/dist/validator/rules-validator.d.ts.map +1 -0
  211. package/dist/validator/rules-validator.js +306 -0
  212. package/dist/validator/rules-validator.js.map +1 -0
  213. package/dist/validator/sdk-validator.d.ts +58 -0
  214. package/dist/validator/sdk-validator.d.ts.map +1 -0
  215. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  216. package/dist/validator/sdk-validator.js.map +1 -0
  217. package/dist/validator/structural-validator.d.ts +11 -0
  218. package/dist/validator/structural-validator.d.ts.map +1 -0
  219. package/dist/validator/structural-validator.js +549 -0
  220. package/dist/validator/structural-validator.js.map +1 -0
  221. package/dist/validator/utils.d.ts +51 -0
  222. package/dist/validator/utils.d.ts.map +1 -0
  223. package/dist/validator/utils.js +132 -0
  224. package/dist/validator/utils.js.map +1 -0
  225. package/package.json +44 -12
  226. package/CLAUDE.md +0 -109
  227. package/api/analyze-feed.js +0 -140
  228. package/api/badge.js +0 -185
  229. package/api/benchmark.js +0 -177
  230. package/api/directory-stats.ts +0 -29
  231. package/api/directory.ts +0 -73
  232. package/api/generate-compliance.js +0 -143
  233. package/api/generate-schema.js +0 -457
  234. package/api/generate.js +0 -132
  235. package/api/security-scan.js +0 -133
  236. package/api/simulate.js +0 -187
  237. package/api/tsconfig.json +0 -10
  238. package/api/validate.js +0 -1351
  239. package/apify-actor/.actor/actor.json +0 -68
  240. package/apify-actor/.actor/input_schema.json +0 -32
  241. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  242. package/apify-actor/Dockerfile +0 -8
  243. package/apify-actor/README.md +0 -166
  244. package/apify-actor/main.ts +0 -111
  245. package/apify-actor/package.json +0 -17
  246. package/apify-actor/src/main.js +0 -199
  247. package/docs/BRAND-IDENTITY.md +0 -238
  248. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  249. package/drizzle/0000_black_king_cobra.sql +0 -39
  250. package/drizzle/meta/0000_snapshot.json +0 -309
  251. package/drizzle/meta/_journal.json +0 -13
  252. package/drizzle.config.ts +0 -10
  253. package/public/.well-known/ucp +0 -25
  254. package/public/android-chrome-192x192.png +0 -0
  255. package/public/android-chrome-512x512.png +0 -0
  256. package/public/apple-touch-icon.png +0 -0
  257. package/public/brand.css +0 -321
  258. package/public/directory.html +0 -701
  259. package/public/favicon-16x16.png +0 -0
  260. package/public/favicon-32x32.png +0 -0
  261. package/public/favicon.ico +0 -0
  262. package/public/guides/bigcommerce.html +0 -743
  263. package/public/guides/fastucp.html +0 -838
  264. package/public/guides/magento.html +0 -779
  265. package/public/guides/shopify.html +0 -726
  266. package/public/guides/squarespace.html +0 -749
  267. package/public/guides/wix.html +0 -747
  268. package/public/guides/woocommerce.html +0 -733
  269. package/public/index.html +0 -3835
  270. package/public/learn.html +0 -396
  271. package/public/logo.jpeg +0 -0
  272. package/public/og-image-icon.png +0 -0
  273. package/public/og-image.png +0 -0
  274. package/public/robots.txt +0 -6
  275. package/public/site.webmanifest +0 -31
  276. package/public/sitemap.xml +0 -69
  277. package/public/social/linkedin-banner-1128x191.png +0 -0
  278. package/public/social/temp.PNG +0 -0
  279. package/public/social/x-header-1500x500.png +0 -0
  280. package/public/verify.html +0 -410
  281. package/scripts/generate-favicons.js +0 -44
  282. package/scripts/generate-ico.js +0 -23
  283. package/scripts/generate-og-image.js +0 -45
  284. package/scripts/reset-db.ts +0 -77
  285. package/scripts/seed-db.ts +0 -71
  286. package/scripts/setup-benchmark-db.js +0 -70
  287. package/src/api/server.ts +0 -266
  288. package/src/cli/index.ts +0 -302
  289. package/src/compliance/compliance-generator.ts +0 -452
  290. package/src/compliance/index.ts +0 -28
  291. package/src/compliance/types.ts +0 -170
  292. package/src/db/index.ts +0 -28
  293. package/src/db/schema.ts +0 -84
  294. package/src/feed-analyzer/index.ts +0 -34
  295. package/src/feed-analyzer/types.ts +0 -354
  296. package/src/generator/key-generator.ts +0 -124
  297. package/src/generator/profile-builder.ts +0 -402
  298. package/src/index.ts +0 -105
  299. package/src/security/security-scanner.ts +0 -604
  300. package/src/security/types.ts +0 -55
  301. package/src/services/directory.ts +0 -434
  302. package/src/simulator/agent-simulator.ts +0 -941
  303. package/src/types/generator.ts +0 -140
  304. package/src/types/ucp-profile.ts +0 -140
  305. package/src/types/validation.ts +0 -89
  306. package/src/validator/index.ts +0 -194
  307. package/src/validator/network-validator.ts +0 -417
  308. package/src/validator/rules-validator.ts +0 -297
  309. package/src/validator/structural-validator.ts +0 -476
  310. package/tests/fixtures/non-compliant-profile.json +0 -25
  311. package/tests/fixtures/official-sample-profile.json +0 -75
  312. package/tests/integration/benchmark.test.ts +0 -207
  313. package/tests/integration/database.test.ts +0 -163
  314. package/tests/integration/directory-api.test.ts +0 -268
  315. package/tests/integration/simulate-api.test.ts +0 -230
  316. package/tests/integration/validate-api.test.ts +0 -269
  317. package/tests/setup.ts +0 -15
  318. package/tests/unit/agent-simulator.test.ts +0 -575
  319. package/tests/unit/compliance-generator.test.ts +0 -374
  320. package/tests/unit/directory-service.test.ts +0 -272
  321. package/tests/unit/feed-analyzer.test.ts +0 -517
  322. package/tests/unit/lint-suggestions.test.ts +0 -423
  323. package/tests/unit/official-samples.test.ts +0 -211
  324. package/tests/unit/pdf-report.test.ts +0 -390
  325. package/tests/unit/sdk-validator.test.ts +0 -531
  326. package/tests/unit/security-scanner.test.ts +0 -410
  327. package/tests/unit/validation.test.ts +0 -390
  328. package/tsconfig.json +0 -20
  329. package/vercel.json +0 -34
  330. package/vitest.config.ts +0 -22
@@ -1,417 +0,0 @@
1
- /**
2
- * Network Validator
3
- * Validates UCP profile with network checks (fetches remote schemas)
4
- */
5
-
6
- import type { UcpProfile } from '../types/ucp-profile.js';
7
- import type { ValidationIssue, FetchResult, SchemaCacheEntry } from '../types/validation.js';
8
- import { ValidationErrorCodes } from '../types/validation.js';
9
-
10
- // Simple in-memory cache for schema fetches
11
- const schemaCache = new Map<string, SchemaCacheEntry>();
12
- const DEFAULT_CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
13
- const DEFAULT_TIMEOUT_MS = 10000; // 10 seconds
14
-
15
- export interface NetworkValidationOptions {
16
- timeoutMs?: number;
17
- cacheTtlMs?: number;
18
- skipSchemaFetch?: boolean;
19
- }
20
-
21
- /**
22
- * Validate UCP profile with network checks
23
- */
24
- export async function validateNetwork(
25
- profile: UcpProfile,
26
- options: NetworkValidationOptions = {}
27
- ): Promise<ValidationIssue[]> {
28
- const issues: ValidationIssue[] = [];
29
- const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
30
- const cacheTtlMs = options.cacheTtlMs || DEFAULT_CACHE_TTL_MS;
31
-
32
- if (options.skipSchemaFetch) {
33
- return issues;
34
- }
35
-
36
- const capabilities = profile.ucp.capabilities || [];
37
-
38
- // Validate each capability's schema
39
- for (let i = 0; i < capabilities.length; i++) {
40
- const cap = capabilities[i];
41
- const path = `$.ucp.capabilities[${i}]`;
42
-
43
- if (cap.schema) {
44
- const schemaIssues = await validateCapabilitySchema(
45
- cap.schema,
46
- cap.name,
47
- cap.version,
48
- path,
49
- timeoutMs,
50
- cacheTtlMs
51
- );
52
- issues.push(...schemaIssues);
53
- }
54
- }
55
-
56
- return issues;
57
- }
58
-
59
- /**
60
- * Validate remote profile fetch
61
- */
62
- export async function validateRemoteProfile(
63
- domain: string,
64
- options: NetworkValidationOptions = {}
65
- ): Promise<{ profile: UcpProfile | null; profileUrl?: string; issues: ValidationIssue[] }> {
66
- const issues: ValidationIssue[] = [];
67
- const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
68
-
69
- // Try both /.well-known/ucp and /.well-known/ucp.json
70
- const urls = [
71
- `https://${domain}/.well-known/ucp`,
72
- `https://${domain}/.well-known/ucp.json`,
73
- ];
74
-
75
- for (const profileUrl of urls) {
76
- const result = await fetchProfileWithTimeout(profileUrl, timeoutMs);
77
-
78
- if (!result.success) {
79
- // Try next URL
80
- continue;
81
- }
82
-
83
- // Verify it's an object with ucp field
84
- if (!result.data || typeof result.data !== 'object') {
85
- continue;
86
- }
87
-
88
- const profileData = result.data as Record<string, unknown>;
89
- if (!profileData.ucp) {
90
- continue;
91
- }
92
-
93
- return { profile: result.data as UcpProfile, profileUrl, issues };
94
- }
95
-
96
- // All URLs failed
97
- issues.push({
98
- severity: 'error',
99
- code: ValidationErrorCodes.PROFILE_FETCH_FAILED,
100
- path: '$.well-known/ucp',
101
- message: 'No UCP profile found at /.well-known/ucp or /.well-known/ucp.json',
102
- hint: 'Check that the profile is accessible and returns valid JSON',
103
- });
104
- return { profile: null, issues };
105
- }
106
-
107
- /**
108
- * Validate a capability's schema (fetch and check self-describing)
109
- */
110
- async function validateCapabilitySchema(
111
- schemaUrl: string,
112
- expectedName: string,
113
- expectedVersion: string,
114
- basePath: string,
115
- timeoutMs: number,
116
- cacheTtlMs: number
117
- ): Promise<ValidationIssue[]> {
118
- const issues: ValidationIssue[] = [];
119
-
120
- // Check cache first
121
- const cached = getCachedSchema(schemaUrl, cacheTtlMs);
122
- let schemaData: Record<string, unknown>;
123
-
124
- if (cached) {
125
- schemaData = cached;
126
- } else {
127
- // Fetch schema
128
- const result = await fetchWithTimeout<Record<string, unknown>>(schemaUrl, timeoutMs);
129
-
130
- if (!result.success) {
131
- issues.push({
132
- severity: 'warn',
133
- code: ValidationErrorCodes.SCHEMA_FETCH_FAILED,
134
- path: `${basePath}.schema`,
135
- message: `Failed to fetch schema from ${schemaUrl}`,
136
- hint: result.error || 'Schema URL may be incorrect or temporarily unavailable',
137
- });
138
- return issues;
139
- }
140
-
141
- if (!result.data) {
142
- issues.push({
143
- severity: 'warn',
144
- code: ValidationErrorCodes.SCHEMA_FETCH_FAILED,
145
- path: `${basePath}.schema`,
146
- message: `Schema response is empty`,
147
- });
148
- return issues;
149
- }
150
-
151
- schemaData = result.data;
152
-
153
- // Cache the schema
154
- cacheSchema(schemaUrl, schemaData, result.etag, cacheTtlMs);
155
- }
156
-
157
- // Check if schema is self-describing
158
- const selfDescribingIssues = validateSelfDescribingSchema(
159
- schemaData,
160
- expectedName,
161
- expectedVersion,
162
- basePath
163
- );
164
- issues.push(...selfDescribingIssues);
165
-
166
- return issues;
167
- }
168
-
169
- /**
170
- * Validate schema is self-describing (contains name and version matching capability)
171
- */
172
- function validateSelfDescribingSchema(
173
- schema: Record<string, unknown>,
174
- expectedName: string,
175
- expectedVersion: string,
176
- basePath: string
177
- ): ValidationIssue[] {
178
- const issues: ValidationIssue[] = [];
179
-
180
- // Check for $id or name field
181
- const schemaName = (schema.$id as string) || (schema.name as string);
182
- const schemaVersion = schema.version as string;
183
-
184
- if (!schemaName && !schema.$id) {
185
- issues.push({
186
- severity: 'info',
187
- code: ValidationErrorCodes.SCHEMA_NOT_SELF_DESCRIBING,
188
- path: `${basePath}.schema`,
189
- message: 'Schema does not contain self-describing $id or name field',
190
- hint: 'Consider adding $id field to schema for better discoverability',
191
- });
192
- }
193
-
194
- // If schema has a name, check it contains the capability name
195
- if (schemaName) {
196
- // Extract capability name from schema $id if it's a URL
197
- const nameFromId = extractNameFromSchemaId(schemaName);
198
- if (nameFromId && !expectedName.includes(nameFromId) && !nameFromId.includes(expectedName.split('.').pop() || '')) {
199
- issues.push({
200
- severity: 'warn',
201
- code: ValidationErrorCodes.SCHEMA_NAME_MISMATCH,
202
- path: `${basePath}.schema`,
203
- message: `Schema name "${nameFromId}" may not match capability "${expectedName}"`,
204
- });
205
- }
206
- }
207
-
208
- // Check version if present
209
- if (schemaVersion && schemaVersion !== expectedVersion) {
210
- issues.push({
211
- severity: 'info',
212
- code: ValidationErrorCodes.SCHEMA_VERSION_MISMATCH,
213
- path: `${basePath}.schema`,
214
- message: `Schema version "${schemaVersion}" differs from capability version "${expectedVersion}"`,
215
- hint: 'Ensure schema and capability versions are aligned',
216
- });
217
- }
218
-
219
- return issues;
220
- }
221
-
222
- /**
223
- * Extract capability name from schema $id URL
224
- */
225
- function extractNameFromSchemaId(schemaId: string): string | null {
226
- try {
227
- const url = new URL(schemaId);
228
- // Extract last path segment without .json extension
229
- const pathParts = url.pathname.split('/').filter(Boolean);
230
- const lastPart = pathParts[pathParts.length - 1] || '';
231
- return lastPart.replace('.json', '');
232
- } catch {
233
- // Not a URL, return as-is
234
- return schemaId;
235
- }
236
- }
237
-
238
- /**
239
- * Fetch profile URL with timeout, checking for HTML responses
240
- */
241
- async function fetchProfileWithTimeout(
242
- url: string,
243
- timeoutMs: number
244
- ): Promise<FetchResult<unknown>> {
245
- const controller = new AbortController();
246
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
247
-
248
- try {
249
- const response = await fetch(url, {
250
- signal: controller.signal,
251
- headers: {
252
- 'Accept': 'application/json',
253
- 'User-Agent': 'UCP-Profile-Validator/1.0',
254
- },
255
- });
256
-
257
- clearTimeout(timeoutId);
258
-
259
- if (!response.ok) {
260
- return {
261
- success: false,
262
- error: `HTTP ${response.status}: ${response.statusText}`,
263
- statusCode: response.status,
264
- };
265
- }
266
-
267
- const text = await response.text();
268
-
269
- // Check if response looks like JSON (not HTML)
270
- if (text.trim().startsWith('<')) {
271
- return {
272
- success: false,
273
- error: 'Response is HTML, not JSON',
274
- };
275
- }
276
-
277
- const data = JSON.parse(text);
278
- const etag = response.headers.get('etag') || undefined;
279
-
280
- return {
281
- success: true,
282
- data,
283
- statusCode: response.status,
284
- etag,
285
- };
286
- } catch (error) {
287
- clearTimeout(timeoutId);
288
-
289
- if (error instanceof Error) {
290
- if (error.name === 'AbortError') {
291
- return {
292
- success: false,
293
- error: `Request timed out after ${timeoutMs}ms`,
294
- };
295
- }
296
- return {
297
- success: false,
298
- error: error.message,
299
- };
300
- }
301
-
302
- return {
303
- success: false,
304
- error: 'Unknown error occurred',
305
- };
306
- }
307
- }
308
-
309
- /**
310
- * Fetch URL with timeout
311
- */
312
- async function fetchWithTimeout<T>(
313
- url: string,
314
- timeoutMs: number
315
- ): Promise<FetchResult<T>> {
316
- const controller = new AbortController();
317
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
318
-
319
- try {
320
- const response = await fetch(url, {
321
- signal: controller.signal,
322
- headers: {
323
- 'Accept': 'application/json',
324
- 'User-Agent': 'UCP-Profile-Validator/1.0',
325
- },
326
- });
327
-
328
- clearTimeout(timeoutId);
329
-
330
- if (!response.ok) {
331
- return {
332
- success: false,
333
- error: `HTTP ${response.status}: ${response.statusText}`,
334
- statusCode: response.status,
335
- };
336
- }
337
-
338
- const data = await response.json() as T;
339
- const etag = response.headers.get('etag') || undefined;
340
-
341
- return {
342
- success: true,
343
- data,
344
- statusCode: response.status,
345
- etag,
346
- };
347
- } catch (error) {
348
- clearTimeout(timeoutId);
349
-
350
- if (error instanceof Error) {
351
- if (error.name === 'AbortError') {
352
- return {
353
- success: false,
354
- error: `Request timed out after ${timeoutMs}ms`,
355
- };
356
- }
357
- return {
358
- success: false,
359
- error: error.message,
360
- };
361
- }
362
-
363
- return {
364
- success: false,
365
- error: 'Unknown error occurred',
366
- };
367
- }
368
- }
369
-
370
- /**
371
- * Get cached schema if valid
372
- */
373
- function getCachedSchema(
374
- url: string,
375
- cacheTtlMs: number
376
- ): Record<string, unknown> | null {
377
- const cached = schemaCache.get(url);
378
- if (!cached) {
379
- return null;
380
- }
381
-
382
- const now = new Date().toISOString();
383
- if (cached.expiresAt < now) {
384
- schemaCache.delete(url);
385
- return null;
386
- }
387
-
388
- return cached.body;
389
- }
390
-
391
- /**
392
- * Cache a schema
393
- */
394
- function cacheSchema(
395
- url: string,
396
- body: Record<string, unknown>,
397
- etag: string | undefined,
398
- cacheTtlMs: number
399
- ): void {
400
- const now = new Date();
401
- const expiresAt = new Date(now.getTime() + cacheTtlMs);
402
-
403
- schemaCache.set(url, {
404
- url,
405
- etag,
406
- fetchedAt: now.toISOString(),
407
- body,
408
- expiresAt: expiresAt.toISOString(),
409
- });
410
- }
411
-
412
- /**
413
- * Clear the schema cache
414
- */
415
- export function clearSchemaCache(): void {
416
- schemaCache.clear();
417
- }
@@ -1,297 +0,0 @@
1
- /**
2
- * UCP Rules Validator
3
- * Validates UCP-specific business rules (no network calls)
4
- */
5
-
6
- import type { UcpProfile, UcpCapability } from '../types/ucp-profile.js';
7
- import type { ValidationIssue } from '../types/validation.js';
8
- import { ValidationErrorCodes } from '../types/validation.js';
9
- import { CAPABILITY_NAMESPACES, KNOWN_CAPABILITIES } from '../types/ucp-profile.js';
10
-
11
- /**
12
- * Validate UCP business rules
13
- */
14
- export function validateRules(profile: UcpProfile): ValidationIssue[] {
15
- const issues: ValidationIssue[] = [];
16
-
17
- // Validate namespace/origin binding for capabilities
18
- issues.push(...validateNamespaceOrigins(profile));
19
-
20
- // Validate extension chains (no orphaned extends)
21
- issues.push(...validateExtensions(profile));
22
-
23
- // Validate endpoint rules
24
- issues.push(...validateEndpoints(profile));
25
-
26
- // Validate signing keys if Order capability is present
27
- issues.push(...validateSigningKeysRequirement(profile));
28
-
29
- return issues;
30
- }
31
-
32
- /**
33
- * Validate namespace and URL origin binding
34
- * - dev.ucp.* capabilities must have spec/schema from ucp.dev
35
- * - com.vendor.* capabilities must have spec/schema from vendor's domain
36
- */
37
- function validateNamespaceOrigins(profile: UcpProfile): ValidationIssue[] {
38
- const issues: ValidationIssue[] = [];
39
- const capabilities = profile.ucp.capabilities || [];
40
-
41
- for (let i = 0; i < capabilities.length; i++) {
42
- const cap = capabilities[i];
43
- const path = `$.ucp.capabilities[${i}]`;
44
-
45
- // Check dev.ucp.* namespace
46
- if (cap.name.startsWith(CAPABILITY_NAMESPACES.UCP_OFFICIAL)) {
47
- // Spec must be from ucp.dev
48
- if (cap.spec && !isUcpDevOrigin(cap.spec)) {
49
- issues.push({
50
- severity: 'error',
51
- code: ValidationErrorCodes.NS_ORIGIN_MISMATCH,
52
- path: `${path}.spec`,
53
- message: `dev.ucp.* capability spec must be hosted on ucp.dev`,
54
- hint: `Use https://ucp.dev/specification/... instead of "${cap.spec}"`,
55
- });
56
- }
57
-
58
- // Schema must be from ucp.dev
59
- if (cap.schema && !isUcpDevOrigin(cap.schema)) {
60
- issues.push({
61
- severity: 'error',
62
- code: ValidationErrorCodes.NS_ORIGIN_MISMATCH,
63
- path: `${path}.schema`,
64
- message: `dev.ucp.* capability schema must be hosted on ucp.dev`,
65
- hint: `Use https://ucp.dev/schemas/... instead of "${cap.schema}"`,
66
- });
67
- }
68
- }
69
-
70
- // Check vendor namespace (com.vendor.*)
71
- if (cap.name.startsWith(CAPABILITY_NAMESPACES.VENDOR_PREFIX)) {
72
- const vendorDomain = extractVendorDomain(cap.name);
73
- if (vendorDomain) {
74
- // Spec origin should match vendor domain
75
- if (cap.spec && !isOriginFromDomain(cap.spec, vendorDomain)) {
76
- issues.push({
77
- severity: 'warn',
78
- code: ValidationErrorCodes.NS_ORIGIN_MISMATCH,
79
- path: `${path}.spec`,
80
- message: `Vendor capability spec should be hosted on vendor's domain (${vendorDomain})`,
81
- hint: `Consider hosting spec at https://${vendorDomain}/...`,
82
- });
83
- }
84
-
85
- // Schema origin should match vendor domain
86
- if (cap.schema && !isOriginFromDomain(cap.schema, vendorDomain)) {
87
- issues.push({
88
- severity: 'warn',
89
- code: ValidationErrorCodes.NS_ORIGIN_MISMATCH,
90
- path: `${path}.schema`,
91
- message: `Vendor capability schema should be hosted on vendor's domain (${vendorDomain})`,
92
- hint: `Consider hosting schema at https://${vendorDomain}/...`,
93
- });
94
- }
95
- }
96
- }
97
- }
98
-
99
- return issues;
100
- }
101
-
102
- /**
103
- * Validate extension chains - ensure parent capabilities exist
104
- */
105
- function validateExtensions(profile: UcpProfile): ValidationIssue[] {
106
- const issues: ValidationIssue[] = [];
107
- const capabilities = profile.ucp.capabilities || [];
108
-
109
- // Build set of capability names
110
- const capabilityNames = new Set(capabilities.map(c => c.name));
111
-
112
- for (let i = 0; i < capabilities.length; i++) {
113
- const cap = capabilities[i];
114
-
115
- if (cap.extends) {
116
- // Check if parent capability exists in this profile
117
- if (!capabilityNames.has(cap.extends)) {
118
- issues.push({
119
- severity: 'error',
120
- code: ValidationErrorCodes.ORPHANED_EXTENSION,
121
- path: `$.ucp.capabilities[${i}].extends`,
122
- message: `Extension "${cap.name}" references non-existent parent capability "${cap.extends}"`,
123
- hint: `Add "${cap.extends}" to capabilities or remove the extends field`,
124
- });
125
- }
126
- }
127
- }
128
-
129
- return issues;
130
- }
131
-
132
- /**
133
- * Validate endpoint rules (https, no trailing slash)
134
- */
135
- function validateEndpoints(profile: UcpProfile): ValidationIssue[] {
136
- const issues: ValidationIssue[] = [];
137
- const services = profile.ucp.services || {};
138
-
139
- for (const [serviceName, service] of Object.entries(services)) {
140
- const basePath = `$.ucp.services["${serviceName}"]`;
141
-
142
- // Validate REST endpoint
143
- if (service.rest?.endpoint) {
144
- issues.push(...validateEndpoint(service.rest.endpoint, `${basePath}.rest.endpoint`));
145
- }
146
-
147
- // Validate MCP endpoint
148
- if (service.mcp?.endpoint) {
149
- issues.push(...validateEndpoint(service.mcp.endpoint, `${basePath}.mcp.endpoint`));
150
- }
151
-
152
- // Validate A2A agent card URL
153
- if (service.a2a?.agentCard) {
154
- issues.push(...validateEndpoint(service.a2a.agentCard, `${basePath}.a2a.agentCard`));
155
- }
156
- }
157
-
158
- return issues;
159
- }
160
-
161
- /**
162
- * Validate a single endpoint URL
163
- */
164
- function validateEndpoint(endpoint: string, path: string): ValidationIssue[] {
165
- const issues: ValidationIssue[] = [];
166
-
167
- // Must be HTTPS
168
- if (!endpoint.startsWith('https://')) {
169
- issues.push({
170
- severity: 'error',
171
- code: ValidationErrorCodes.ENDPOINT_NOT_HTTPS,
172
- path,
173
- message: `Endpoint must use HTTPS`,
174
- hint: `Change "${endpoint}" to use https://`,
175
- });
176
- }
177
-
178
- // Should not have trailing slash
179
- if (endpoint.endsWith('/')) {
180
- issues.push({
181
- severity: 'warn',
182
- code: ValidationErrorCodes.ENDPOINT_TRAILING_SLASH,
183
- path,
184
- message: `Endpoint should not have a trailing slash`,
185
- hint: `Remove trailing slash from "${endpoint}"`,
186
- });
187
- }
188
-
189
- // Check for private IP ranges (basic check)
190
- if (isPrivateIpEndpoint(endpoint)) {
191
- issues.push({
192
- severity: 'warn',
193
- code: ValidationErrorCodes.PRIVATE_IP_ENDPOINT,
194
- path,
195
- message: `Endpoint appears to use a private IP address`,
196
- hint: `Use a public domain name for production profiles`,
197
- });
198
- }
199
-
200
- return issues;
201
- }
202
-
203
- /**
204
- * Validate signing keys requirement for Order capability
205
- */
206
- function validateSigningKeysRequirement(profile: UcpProfile): ValidationIssue[] {
207
- const issues: ValidationIssue[] = [];
208
- const capabilities = profile.ucp.capabilities || [];
209
-
210
- // Check if Order capability is present
211
- const hasOrderCapability = capabilities.some(
212
- c => c.name === KNOWN_CAPABILITIES.ORDER
213
- );
214
-
215
- if (hasOrderCapability) {
216
- // Signing keys should be present for webhook signing
217
- if (!profile.signing_keys || profile.signing_keys.length === 0) {
218
- issues.push({
219
- severity: 'error',
220
- code: ValidationErrorCodes.MISSING_SIGNING_KEYS,
221
- path: '$.signing_keys',
222
- message: `Order capability requires signing_keys for webhook verification`,
223
- hint: `Add signing_keys array with at least one JWK public key`,
224
- });
225
- }
226
- }
227
-
228
- return issues;
229
- }
230
-
231
- /**
232
- * Check if URL is from ucp.dev origin
233
- */
234
- function isUcpDevOrigin(url: string): boolean {
235
- try {
236
- const parsed = new URL(url);
237
- return parsed.hostname === 'ucp.dev' || parsed.hostname.endsWith('.ucp.dev');
238
- } catch {
239
- return false;
240
- }
241
- }
242
-
243
- /**
244
- * Extract vendor domain from capability name
245
- * e.g., "com.example.feature" -> "example.com"
246
- */
247
- function extractVendorDomain(name: string): string | null {
248
- if (!name.startsWith('com.')) {
249
- return null;
250
- }
251
-
252
- const parts = name.split('.');
253
- if (parts.length < 3) {
254
- return null;
255
- }
256
-
257
- // "com.example.feature" -> "example.com"
258
- return `${parts[1]}.com`;
259
- }
260
-
261
- /**
262
- * Check if URL origin matches expected domain
263
- */
264
- function isOriginFromDomain(url: string, domain: string): boolean {
265
- try {
266
- const parsed = new URL(url);
267
- return parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`);
268
- } catch {
269
- return false;
270
- }
271
- }
272
-
273
- /**
274
- * Check if endpoint uses private IP address
275
- */
276
- function isPrivateIpEndpoint(endpoint: string): boolean {
277
- try {
278
- const parsed = new URL(endpoint);
279
- const hostname = parsed.hostname;
280
-
281
- // Check for localhost
282
- if (hostname === 'localhost' || hostname === '127.0.0.1') {
283
- return true;
284
- }
285
-
286
- // Check for private IP ranges (simplified)
287
- if (hostname.startsWith('10.') ||
288
- hostname.startsWith('192.168.') ||
289
- hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./)) {
290
- return true;
291
- }
292
-
293
- return false;
294
- } catch {
295
- return false;
296
- }
297
- }