@ucptools/validator 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/.claude/settings.local.json +60 -0
  2. package/.vercel/README.txt +11 -0
  3. package/.vercel/project.json +1 -0
  4. package/dist/cli/index.d.ts +6 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +279 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/compliance/compliance-generator.d.ts +34 -0
  9. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  10. package/dist/compliance/compliance-generator.js +320 -0
  11. package/dist/compliance/compliance-generator.js.map +1 -0
  12. package/dist/compliance/index.d.ts +8 -0
  13. package/dist/compliance/index.d.ts.map +1 -0
  14. package/dist/compliance/index.js +17 -0
  15. package/dist/compliance/index.js.map +1 -0
  16. package/dist/compliance/templates.d.ts +34 -0
  17. package/dist/compliance/templates.d.ts.map +1 -0
  18. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  19. package/dist/compliance/templates.js.map +1 -0
  20. package/dist/compliance/types.d.ts +64 -0
  21. package/dist/compliance/types.d.ts.map +1 -0
  22. package/dist/compliance/types.js +64 -0
  23. package/dist/compliance/types.js.map +1 -0
  24. package/dist/db/index.d.ts +11 -0
  25. package/dist/db/index.d.ts.map +1 -0
  26. package/dist/db/index.js +63 -0
  27. package/dist/db/index.js.map +1 -0
  28. package/dist/db/schema.d.ts +444 -0
  29. package/dist/db/schema.d.ts.map +1 -0
  30. package/dist/db/schema.js +65 -0
  31. package/dist/db/schema.js.map +1 -0
  32. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  33. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  34. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
  35. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  36. package/dist/feed-analyzer/index.d.ts +8 -0
  37. package/dist/feed-analyzer/index.d.ts.map +1 -0
  38. package/dist/feed-analyzer/index.js +19 -0
  39. package/dist/feed-analyzer/index.js.map +1 -0
  40. package/dist/feed-analyzer/types.d.ts +204 -0
  41. package/dist/feed-analyzer/types.d.ts.map +1 -0
  42. package/dist/feed-analyzer/types.js +162 -0
  43. package/dist/feed-analyzer/types.js.map +1 -0
  44. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  45. package/dist/generator/index.d.ts.map +1 -0
  46. package/dist/generator/index.js +13 -0
  47. package/dist/generator/index.js.map +1 -0
  48. package/dist/generator/key-generator.d.ts +24 -0
  49. package/dist/generator/key-generator.d.ts.map +1 -0
  50. package/dist/generator/key-generator.js +144 -0
  51. package/dist/generator/key-generator.js.map +1 -0
  52. package/dist/generator/profile-builder.d.ts +15 -0
  53. package/dist/generator/profile-builder.d.ts.map +1 -0
  54. package/dist/generator/profile-builder.js +338 -0
  55. package/dist/generator/profile-builder.js.map +1 -0
  56. package/dist/hosting/artifacts-generator.d.ts +10 -0
  57. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  58. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  59. package/dist/hosting/artifacts-generator.js.map +1 -0
  60. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  61. package/dist/hosting/index.d.ts.map +1 -0
  62. package/dist/hosting/index.js +10 -0
  63. package/dist/hosting/index.js.map +1 -0
  64. package/dist/index.d.ts +18 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +78 -0
  67. package/dist/index.js.map +1 -0
  68. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  69. package/dist/security/index.d.ts.map +1 -0
  70. package/dist/security/index.js +12 -0
  71. package/dist/security/index.js.map +1 -0
  72. package/dist/security/security-scanner.d.ts +10 -0
  73. package/dist/security/security-scanner.d.ts.map +1 -0
  74. package/dist/security/security-scanner.js +541 -0
  75. package/dist/security/security-scanner.js.map +1 -0
  76. package/dist/security/types.d.ts +48 -0
  77. package/dist/security/types.d.ts.map +1 -0
  78. package/dist/security/types.js +21 -0
  79. package/dist/security/types.js.map +1 -0
  80. package/dist/services/directory.d.ts +104 -0
  81. package/dist/services/directory.d.ts.map +1 -0
  82. package/dist/services/directory.js +333 -0
  83. package/dist/services/directory.js.map +1 -0
  84. package/dist/simulator/agent-simulator.d.ts +69 -0
  85. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  86. package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
  87. package/dist/simulator/agent-simulator.js.map +1 -0
  88. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  89. package/dist/simulator/index.d.ts.map +1 -0
  90. package/dist/simulator/index.js +23 -0
  91. package/dist/simulator/index.js.map +1 -0
  92. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
  93. package/dist/simulator/types.d.ts.map +1 -0
  94. package/dist/simulator/types.js +18 -0
  95. package/dist/simulator/types.js.map +1 -0
  96. package/dist/types/generator.d.ts +106 -0
  97. package/dist/types/generator.d.ts.map +1 -0
  98. package/dist/types/generator.js +6 -0
  99. package/dist/types/generator.js.map +1 -0
  100. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  101. package/dist/types/index.d.ts.map +1 -0
  102. package/dist/types/index.js +23 -0
  103. package/dist/types/index.js.map +1 -0
  104. package/dist/types/ucp-profile.d.ts +103 -0
  105. package/dist/types/ucp-profile.d.ts.map +1 -0
  106. package/dist/types/ucp-profile.js +45 -0
  107. package/dist/types/ucp-profile.js.map +1 -0
  108. package/dist/types/validation.d.ts +68 -0
  109. package/dist/types/validation.d.ts.map +1 -0
  110. package/dist/types/validation.js +32 -0
  111. package/dist/types/validation.js.map +1 -0
  112. package/dist/validator/index.d.ts +26 -0
  113. package/dist/validator/index.d.ts.map +1 -0
  114. package/dist/validator/index.js +161 -0
  115. package/dist/validator/index.js.map +1 -0
  116. package/dist/validator/network-validator.d.ts +28 -0
  117. package/dist/validator/network-validator.d.ts.map +1 -0
  118. package/dist/validator/network-validator.js +319 -0
  119. package/dist/validator/network-validator.js.map +1 -0
  120. package/dist/validator/rules-validator.d.ts +11 -0
  121. package/dist/validator/rules-validator.d.ts.map +1 -0
  122. package/dist/validator/rules-validator.js +257 -0
  123. package/dist/validator/rules-validator.js.map +1 -0
  124. package/dist/validator/sdk-validator.d.ts +58 -0
  125. package/dist/validator/sdk-validator.d.ts.map +1 -0
  126. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  127. package/dist/validator/sdk-validator.js.map +1 -0
  128. package/dist/validator/structural-validator.d.ts +11 -0
  129. package/dist/validator/structural-validator.d.ts.map +1 -0
  130. package/dist/validator/structural-validator.js +415 -0
  131. package/dist/validator/structural-validator.js.map +1 -0
  132. package/package.json +1 -1
  133. package/publish-output.txt +0 -0
  134. package/CLAUDE.md +0 -109
  135. package/api/analyze-feed.js +0 -140
  136. package/api/badge.js +0 -185
  137. package/api/benchmark.js +0 -177
  138. package/api/directory-stats.ts +0 -29
  139. package/api/directory.ts +0 -73
  140. package/api/generate-compliance.js +0 -143
  141. package/api/generate-schema.js +0 -457
  142. package/api/generate.js +0 -132
  143. package/api/security-scan.js +0 -133
  144. package/api/simulate.js +0 -187
  145. package/api/tsconfig.json +0 -10
  146. package/api/validate.js +0 -1351
  147. package/apify-actor/.actor/actor.json +0 -68
  148. package/apify-actor/.actor/input_schema.json +0 -32
  149. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  150. package/apify-actor/Dockerfile +0 -8
  151. package/apify-actor/README.md +0 -166
  152. package/apify-actor/main.ts +0 -111
  153. package/apify-actor/package.json +0 -17
  154. package/apify-actor/src/main.js +0 -199
  155. package/docs/BRAND-IDENTITY.md +0 -238
  156. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  157. package/drizzle/0000_black_king_cobra.sql +0 -39
  158. package/drizzle/meta/0000_snapshot.json +0 -309
  159. package/drizzle/meta/_journal.json +0 -13
  160. package/drizzle.config.ts +0 -10
  161. package/public/.well-known/ucp +0 -25
  162. package/public/android-chrome-192x192.png +0 -0
  163. package/public/android-chrome-512x512.png +0 -0
  164. package/public/apple-touch-icon.png +0 -0
  165. package/public/brand.css +0 -321
  166. package/public/directory.html +0 -701
  167. package/public/favicon-16x16.png +0 -0
  168. package/public/favicon-32x32.png +0 -0
  169. package/public/favicon.ico +0 -0
  170. package/public/guides/bigcommerce.html +0 -743
  171. package/public/guides/fastucp.html +0 -838
  172. package/public/guides/magento.html +0 -779
  173. package/public/guides/shopify.html +0 -726
  174. package/public/guides/squarespace.html +0 -749
  175. package/public/guides/wix.html +0 -747
  176. package/public/guides/woocommerce.html +0 -733
  177. package/public/index.html +0 -3835
  178. package/public/learn.html +0 -396
  179. package/public/logo.jpeg +0 -0
  180. package/public/og-image-icon.png +0 -0
  181. package/public/og-image.png +0 -0
  182. package/public/robots.txt +0 -6
  183. package/public/site.webmanifest +0 -31
  184. package/public/sitemap.xml +0 -69
  185. package/public/social/linkedin-banner-1128x191.png +0 -0
  186. package/public/social/temp.PNG +0 -0
  187. package/public/social/x-header-1500x500.png +0 -0
  188. package/public/verify.html +0 -410
  189. package/scripts/generate-favicons.js +0 -44
  190. package/scripts/generate-ico.js +0 -23
  191. package/scripts/generate-og-image.js +0 -45
  192. package/scripts/reset-db.ts +0 -77
  193. package/scripts/seed-db.ts +0 -71
  194. package/scripts/setup-benchmark-db.js +0 -70
  195. package/src/api/server.ts +0 -266
  196. package/src/cli/index.ts +0 -302
  197. package/src/compliance/compliance-generator.ts +0 -452
  198. package/src/compliance/index.ts +0 -28
  199. package/src/compliance/types.ts +0 -170
  200. package/src/db/index.ts +0 -28
  201. package/src/db/schema.ts +0 -84
  202. package/src/feed-analyzer/index.ts +0 -34
  203. package/src/feed-analyzer/types.ts +0 -354
  204. package/src/generator/key-generator.ts +0 -124
  205. package/src/generator/profile-builder.ts +0 -402
  206. package/src/index.ts +0 -105
  207. package/src/security/security-scanner.ts +0 -604
  208. package/src/security/types.ts +0 -55
  209. package/src/services/directory.ts +0 -434
  210. package/src/types/generator.ts +0 -140
  211. package/src/types/ucp-profile.ts +0 -140
  212. package/src/types/validation.ts +0 -89
  213. package/src/validator/index.ts +0 -194
  214. package/src/validator/network-validator.ts +0 -417
  215. package/src/validator/rules-validator.ts +0 -297
  216. package/src/validator/structural-validator.ts +0 -476
  217. package/tests/fixtures/non-compliant-profile.json +0 -25
  218. package/tests/fixtures/official-sample-profile.json +0 -75
  219. package/tests/integration/benchmark.test.ts +0 -207
  220. package/tests/integration/database.test.ts +0 -163
  221. package/tests/integration/directory-api.test.ts +0 -268
  222. package/tests/integration/simulate-api.test.ts +0 -230
  223. package/tests/integration/validate-api.test.ts +0 -269
  224. package/tests/setup.ts +0 -15
  225. package/tests/unit/agent-simulator.test.ts +0 -575
  226. package/tests/unit/compliance-generator.test.ts +0 -374
  227. package/tests/unit/directory-service.test.ts +0 -272
  228. package/tests/unit/feed-analyzer.test.ts +0 -517
  229. package/tests/unit/lint-suggestions.test.ts +0 -423
  230. package/tests/unit/official-samples.test.ts +0 -211
  231. package/tests/unit/pdf-report.test.ts +0 -390
  232. package/tests/unit/sdk-validator.test.ts +0 -531
  233. package/tests/unit/security-scanner.test.ts +0 -410
  234. package/tests/unit/validation.test.ts +0 -390
  235. package/vercel.json +0 -34
  236. package/vitest.config.ts +0 -22
@@ -1,89 +0,0 @@
1
- /**
2
- * Validation Types for UCP Profile Validator
3
- */
4
-
5
- // Validation severity levels
6
- export type ValidationSeverity = 'error' | 'warn' | 'info';
7
-
8
- // Validation error codes
9
- export const ValidationErrorCodes = {
10
- // Structural errors
11
- MISSING_UCP_OBJECT: 'UCP_MISSING_ROOT',
12
- MISSING_VERSION: 'UCP_MISSING_VERSION',
13
- INVALID_VERSION_FORMAT: 'UCP_INVALID_VERSION_FORMAT',
14
- MISSING_SERVICES: 'UCP_MISSING_SERVICES',
15
- MISSING_CAPABILITIES: 'UCP_MISSING_CAPABILITIES',
16
- INVALID_SERVICE_STRUCTURE: 'UCP_INVALID_SERVICE',
17
- INVALID_CAPABILITY_STRUCTURE: 'UCP_INVALID_CAPABILITY',
18
-
19
- // UCP rules errors
20
- NS_ORIGIN_MISMATCH: 'UCP_NS_ORIGIN_MISMATCH',
21
- ORPHANED_EXTENSION: 'UCP_ORPHANED_EXTENSION',
22
- ENDPOINT_NOT_HTTPS: 'UCP_ENDPOINT_NOT_HTTPS',
23
- ENDPOINT_TRAILING_SLASH: 'UCP_ENDPOINT_TRAILING_SLASH',
24
- MISSING_SIGNING_KEYS: 'UCP_MISSING_SIGNING_KEYS',
25
- INVALID_SIGNING_KEY: 'UCP_INVALID_SIGNING_KEY',
26
-
27
- // Network validation errors
28
- PROFILE_FETCH_FAILED: 'UCP_PROFILE_FETCH_FAILED',
29
- SCHEMA_FETCH_FAILED: 'UCP_SCHEMA_FETCH_FAILED',
30
- SCHEMA_NOT_SELF_DESCRIBING: 'UCP_SCHEMA_NOT_SELF_DESCRIBING',
31
- SCHEMA_NAME_MISMATCH: 'UCP_SCHEMA_NAME_MISMATCH',
32
- SCHEMA_VERSION_MISMATCH: 'UCP_SCHEMA_VERSION_MISMATCH',
33
- PRIVATE_IP_ENDPOINT: 'UCP_PRIVATE_IP_ENDPOINT',
34
- } as const;
35
-
36
- export type ValidationErrorCode = typeof ValidationErrorCodes[keyof typeof ValidationErrorCodes];
37
-
38
- // Single validation issue
39
- export interface ValidationIssue {
40
- severity: ValidationSeverity;
41
- code: ValidationErrorCode;
42
- path: string; // JSON path (e.g., "$.ucp.capabilities[0].schema")
43
- message: string; // Human-readable message
44
- hint?: string; // Suggestion for fixing
45
- }
46
-
47
- // Validation report
48
- export interface ValidationReport {
49
- ok: boolean;
50
- profile_url?: string; // For remote validation
51
- ucp_version?: string;
52
- issues: ValidationIssue[];
53
- validated_at: string; // ISO timestamp
54
- validation_mode: ValidationMode;
55
- sdk_validation?: { // Official SDK validation info
56
- validated: boolean; // Whether SDK validation passed
57
- sdk_version: string; // @ucp-js/sdk version used
58
- compliant: boolean; // Whether profile is SDK-compliant
59
- };
60
- }
61
-
62
- // Validation modes
63
- export type ValidationMode = 'structural' | 'rules' | 'network' | 'full';
64
-
65
- // Validation options
66
- export interface ValidationOptions {
67
- mode?: ValidationMode;
68
- skipNetworkChecks?: boolean;
69
- timeoutMs?: number;
70
- cacheTtlMs?: number;
71
- }
72
-
73
- // Schema cache entry
74
- export interface SchemaCacheEntry {
75
- url: string;
76
- etag?: string;
77
- fetchedAt: string;
78
- body: Record<string, unknown>;
79
- expiresAt: string;
80
- }
81
-
82
- // Remote fetch result
83
- export interface FetchResult<T> {
84
- success: boolean;
85
- data?: T;
86
- error?: string;
87
- statusCode?: number;
88
- etag?: string;
89
- }
@@ -1,194 +0,0 @@
1
- /**
2
- * UCP Profile Validator
3
- * Main entry point combining structural, rules, and network validation
4
- */
5
-
6
- import type { UcpProfile } from '../types/ucp-profile.js';
7
- import type {
8
- ValidationReport,
9
- ValidationIssue,
10
- ValidationMode,
11
- ValidationOptions,
12
- } from '../types/validation.js';
13
- import { validateStructure } from './structural-validator.js';
14
- import { validateRules } from './rules-validator.js';
15
- import { validateNetwork, validateRemoteProfile, clearSchemaCache } from './network-validator.js';
16
- import { safeValidateWithSdk, getSdkVersion, isSdkCompliant } from './sdk-validator.js';
17
- import type { NetworkValidationOptions } from './network-validator.js';
18
-
19
- export { validateStructure } from './structural-validator.js';
20
- export { validateRules } from './rules-validator.js';
21
- export { validateNetwork, validateRemoteProfile, clearSchemaCache } from './network-validator.js';
22
- export {
23
- safeValidateWithSdk,
24
- validateWithSdk,
25
- getSdkVersion,
26
- isSdkCompliant,
27
- validateServiceWithSdk,
28
- validateCapabilityWithSdk,
29
- validateSigningKeysWithSdk,
30
- } from './sdk-validator.js';
31
-
32
- /**
33
- * Validate a UCP profile (local JSON)
34
- */
35
- export async function validateProfile(
36
- profile: unknown,
37
- options: ValidationOptions = {}
38
- ): Promise<ValidationReport> {
39
- const mode = options.mode || 'full';
40
- const issues: ValidationIssue[] = [];
41
-
42
- // Phase 1: Structural validation (always run)
43
- if (mode === 'structural' || mode === 'rules' || mode === 'full') {
44
- const structuralIssues = validateStructure(profile);
45
- issues.push(...structuralIssues);
46
-
47
- // If structural validation has errors, don't proceed with rules/network
48
- const hasStructuralErrors = structuralIssues.some(i => i.severity === 'error');
49
- if (hasStructuralErrors && mode !== 'structural') {
50
- return buildReport(issues, mode, undefined, profile);
51
- }
52
- }
53
-
54
- // At this point, profile structure is valid
55
- const ucpProfile = profile as UcpProfile;
56
-
57
- // Phase 2: UCP rules validation
58
- if (mode === 'rules' || mode === 'full') {
59
- const rulesIssues = validateRules(ucpProfile);
60
- issues.push(...rulesIssues);
61
- }
62
-
63
- // Phase 3: Network validation (optional)
64
- if (mode === 'network' || mode === 'full') {
65
- if (!options.skipNetworkChecks) {
66
- const networkOptions: NetworkValidationOptions = {
67
- timeoutMs: options.timeoutMs,
68
- cacheTtlMs: options.cacheTtlMs,
69
- };
70
- const networkIssues = await validateNetwork(ucpProfile, networkOptions);
71
- issues.push(...networkIssues);
72
- }
73
- }
74
-
75
- return buildReport(issues, mode, undefined, ucpProfile);
76
- }
77
-
78
- /**
79
- * Validate a remote UCP profile (fetches from domain)
80
- */
81
- export async function validateRemote(
82
- domain: string,
83
- options: ValidationOptions = {}
84
- ): Promise<ValidationReport> {
85
- const issues: ValidationIssue[] = [];
86
-
87
- // Fetch remote profile
88
- const { profile, profileUrl: foundProfileUrl, issues: fetchIssues } = await validateRemoteProfile(domain, {
89
- timeoutMs: options.timeoutMs,
90
- cacheTtlMs: options.cacheTtlMs,
91
- });
92
- issues.push(...fetchIssues);
93
-
94
- const profileUrl = foundProfileUrl || `https://${domain}/.well-known/ucp`;
95
-
96
- if (!profile) {
97
- return buildReport(issues, 'network', profileUrl, undefined);
98
- }
99
-
100
- // Run full validation on fetched profile
101
- const validationResult = await validateProfile(profile, options);
102
- issues.push(...validationResult.issues);
103
-
104
- return buildReport(issues, options.mode || 'full', profileUrl, profile);
105
- }
106
-
107
- /**
108
- * Build validation report
109
- */
110
- function buildReport(
111
- issues: ValidationIssue[],
112
- mode: ValidationMode,
113
- profileUrl?: string,
114
- profile?: unknown
115
- ): ValidationReport {
116
- // Determine if validation passed (no errors)
117
- const hasErrors = issues.some(i => i.severity === 'error');
118
-
119
- // Extract UCP version if available
120
- let ucpVersion: string | undefined;
121
- if (profile && typeof profile === 'object') {
122
- const p = profile as Record<string, unknown>;
123
- if (p.ucp && typeof p.ucp === 'object') {
124
- const ucp = p.ucp as Record<string, unknown>;
125
- if (typeof ucp.version === 'string') {
126
- ucpVersion = ucp.version;
127
- }
128
- }
129
- }
130
-
131
- // Run SDK validation for compliance check
132
- const sdkCompliant = profile ? isSdkCompliant(profile) : false;
133
-
134
- return {
135
- ok: !hasErrors,
136
- profile_url: profileUrl,
137
- ucp_version: ucpVersion,
138
- issues,
139
- validated_at: new Date().toISOString(),
140
- validation_mode: mode,
141
- sdk_validation: {
142
- validated: true,
143
- sdk_version: getSdkVersion(),
144
- compliant: sdkCompliant,
145
- },
146
- };
147
- }
148
-
149
- /**
150
- * Quick validation (structural + rules only, no network)
151
- */
152
- export function validateQuick(profile: unknown): ValidationReport {
153
- const issues: ValidationIssue[] = [];
154
-
155
- // Structural validation
156
- const structuralIssues = validateStructure(profile);
157
- issues.push(...structuralIssues);
158
-
159
- // If structural is OK, run rules validation
160
- const hasStructuralErrors = structuralIssues.some(i => i.severity === 'error');
161
- if (!hasStructuralErrors) {
162
- const rulesIssues = validateRules(profile as UcpProfile);
163
- issues.push(...rulesIssues);
164
- }
165
-
166
- return buildReport(issues, 'rules', undefined, profile);
167
- }
168
-
169
- /**
170
- * Parse and validate JSON string
171
- */
172
- export async function validateJsonString(
173
- json: string,
174
- options: ValidationOptions = {}
175
- ): Promise<ValidationReport> {
176
- try {
177
- const profile = JSON.parse(json);
178
- return validateProfile(profile, options);
179
- } catch (error) {
180
- const message = error instanceof Error ? error.message : 'Invalid JSON';
181
- return {
182
- ok: false,
183
- issues: [{
184
- severity: 'error',
185
- code: 'UCP_MISSING_ROOT' as const,
186
- path: '$',
187
- message: `Failed to parse JSON: ${message}`,
188
- hint: 'Ensure the input is valid JSON',
189
- }],
190
- validated_at: new Date().toISOString(),
191
- validation_mode: options.mode || 'full',
192
- };
193
- }
194
- }
@@ -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
- }