lighthouse 13.5.0 → 13.6.0-dev.20261027

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 (266) hide show
  1. package/.agents/skills/update-ard-port/SKILL.md +171 -0
  2. package/.agents/skills/update-trace-engine/SKILL.md +154 -0
  3. package/cli/bin.d.ts +2 -2
  4. package/cli/bin.js +5 -4
  5. package/cli/cli-flags.d.ts +16 -1
  6. package/cli/cli-flags.js +53 -2
  7. package/cli/printer.js +5 -8
  8. package/cli/run.d.ts +5 -5
  9. package/cli/run.js +97 -47
  10. package/cli/sentry-prompt.js +3 -4
  11. package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +1 -1
  12. package/cli/test/smokehouse/config/exclusions.js +3 -1
  13. package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
  14. package/cli/test/smokehouse/report-assert-test.js +65 -0
  15. package/cli/test/smokehouse/report-assert.js +24 -39
  16. package/cli/test/smokehouse/smokehouse-test.d.ts +2 -0
  17. package/cli/test/smokehouse/smokehouse-test.js +120 -0
  18. package/cli/test/smokehouse/smokehouse.js +37 -15
  19. package/cli/test/smokehouse/version-check-test.js +5 -0
  20. package/cli/test/smokehouse/version-check.d.ts +1 -1
  21. package/cli/test/smokehouse/version-check.js +1 -1
  22. package/commitlint.config.js +1 -1
  23. package/core/audits/accessibility/accesskeys.js +1 -1
  24. package/core/audits/accessibility/aria-allowed-attr.js +1 -1
  25. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  26. package/core/audits/accessibility/aria-command-name.js +1 -1
  27. package/core/audits/accessibility/aria-conditional-attr.js +1 -1
  28. package/core/audits/accessibility/aria-deprecated-role.js +1 -1
  29. package/core/audits/accessibility/aria-dialog-name.js +1 -1
  30. package/core/audits/accessibility/aria-hidden-body.js +1 -1
  31. package/core/audits/accessibility/aria-hidden-focus.js +1 -1
  32. package/core/audits/accessibility/aria-input-field-name.js +1 -1
  33. package/core/audits/accessibility/aria-meter-name.js +1 -1
  34. package/core/audits/accessibility/aria-progressbar-name.js +1 -1
  35. package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
  36. package/core/audits/accessibility/aria-required-attr.js +1 -1
  37. package/core/audits/accessibility/aria-required-children.js +1 -1
  38. package/core/audits/accessibility/aria-required-parent.js +1 -1
  39. package/core/audits/accessibility/aria-roles.js +1 -1
  40. package/core/audits/accessibility/aria-text.js +1 -1
  41. package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
  42. package/core/audits/accessibility/aria-tooltip-name.js +1 -1
  43. package/core/audits/accessibility/aria-treeitem-name.js +1 -1
  44. package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
  45. package/core/audits/accessibility/aria-valid-attr.js +1 -1
  46. package/core/audits/accessibility/autocomplete-valid.js +1 -1
  47. package/core/audits/accessibility/button-name.js +1 -1
  48. package/core/audits/accessibility/bypass.js +1 -1
  49. package/core/audits/accessibility/color-contrast.js +1 -1
  50. package/core/audits/accessibility/definition-list.js +1 -1
  51. package/core/audits/accessibility/dlitem.js +1 -1
  52. package/core/audits/accessibility/document-title.js +1 -1
  53. package/core/audits/accessibility/duplicate-id-aria.js +1 -1
  54. package/core/audits/accessibility/empty-heading.js +1 -1
  55. package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
  56. package/core/audits/accessibility/frame-title.js +1 -1
  57. package/core/audits/accessibility/heading-order.js +1 -1
  58. package/core/audits/accessibility/html-has-lang.js +1 -1
  59. package/core/audits/accessibility/html-lang-valid.js +1 -1
  60. package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
  61. package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
  62. package/core/audits/accessibility/image-alt.js +1 -1
  63. package/core/audits/accessibility/image-redundant-alt.js +1 -1
  64. package/core/audits/accessibility/input-button-name.js +1 -1
  65. package/core/audits/accessibility/input-image-alt.js +1 -1
  66. package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
  67. package/core/audits/accessibility/label.js +1 -1
  68. package/core/audits/accessibility/landmark-one-main.js +1 -1
  69. package/core/audits/accessibility/link-in-text-block.js +1 -1
  70. package/core/audits/accessibility/link-name.js +1 -1
  71. package/core/audits/accessibility/list.js +1 -1
  72. package/core/audits/accessibility/listitem.js +1 -1
  73. package/core/audits/accessibility/meta-refresh.js +1 -1
  74. package/core/audits/accessibility/meta-viewport.js +1 -1
  75. package/core/audits/accessibility/object-alt.js +1 -1
  76. package/core/audits/accessibility/presentation-role-conflict.js +1 -1
  77. package/core/audits/accessibility/select-name.js +1 -1
  78. package/core/audits/accessibility/skip-link.js +1 -1
  79. package/core/audits/accessibility/svg-img-alt.js +1 -1
  80. package/core/audits/accessibility/tabindex.js +1 -1
  81. package/core/audits/accessibility/table-duplicate-name.js +1 -1
  82. package/core/audits/accessibility/table-fake-caption.js +1 -1
  83. package/core/audits/accessibility/target-size.js +1 -1
  84. package/core/audits/accessibility/td-has-header.js +1 -1
  85. package/core/audits/accessibility/td-headers-attr.js +1 -1
  86. package/core/audits/accessibility/th-has-data-cells.js +1 -1
  87. package/core/audits/accessibility/valid-lang.js +1 -1
  88. package/core/audits/accessibility/video-caption.js +1 -1
  89. package/core/audits/agentic/ard-schema.d.ts +13 -0
  90. package/core/audits/agentic/ard-schema.js +81 -33
  91. package/core/audits/agentic/llms-txt.js +5 -2
  92. package/core/audits/baseline.d.ts +29 -7
  93. package/core/audits/baseline.js +19 -6
  94. package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
  95. package/core/audits/insights/insight-audit.d.ts +9 -0
  96. package/core/audits/insights/insight-audit.js +35 -28
  97. package/core/audits/long-tasks.js +2 -1
  98. package/core/audits/predictive-perf.js +2 -2
  99. package/core/audits/screenshot-thumbnails.d.ts +0 -13
  100. package/core/audits/screenshot-thumbnails.js +0 -45
  101. package/core/audits/script-treemap-data.js +26 -6
  102. package/core/audits/seo/robots-txt.js +3 -8
  103. package/core/audits/unsized-images.d.ts +0 -7
  104. package/core/audits/unsized-images.js +7 -26
  105. package/core/computed/lcp-image-record.d.ts +2 -23
  106. package/core/computed/lcp-image-record.js +3 -67
  107. package/core/computed/lcp-network-record.d.ts +24 -0
  108. package/core/computed/lcp-network-record.js +74 -0
  109. package/core/computed/metrics/lantern-metric.js +1 -1
  110. package/core/computed/metrics/lcp-breakdown.js +6 -6
  111. package/core/computed/metrics/time-to-first-byte.js +5 -3
  112. package/core/computed/navigation-insights.js +3 -2
  113. package/core/computed/page-dependency-graph.js +55 -5
  114. package/core/computed/speedline.js +20 -5
  115. package/core/computed/trace-engine-result.js +25 -5
  116. package/core/computed/unused-css.js +15 -2
  117. package/core/config/config-helpers.d.ts +5 -0
  118. package/core/config/config-helpers.js +8 -28
  119. package/core/config/config.js +1 -2
  120. package/core/config/constants.d.ts +2 -28
  121. package/core/config/lr-desktop-config.js +13 -0
  122. package/core/config/lr-mobile-config.js +13 -0
  123. package/core/gather/base-artifacts.js +7 -1
  124. package/core/gather/driver/storage.js +3 -2
  125. package/core/gather/gatherers/agentic/ard.d.ts +59 -4
  126. package/core/gather/gatherers/agentic/ard.js +218 -56
  127. package/core/gather/gatherers/full-page-screenshot.js +2 -2
  128. package/core/gather/gatherers/image-elements.d.ts +7 -0
  129. package/core/gather/gatherers/image-elements.js +75 -24
  130. package/core/gather/gatherers/inspector-issues.js +2 -0
  131. package/core/gather/gatherers/js-usage.js +2 -0
  132. package/core/gather/gatherers/scripts.d.ts +16 -3
  133. package/core/gather/gatherers/scripts.js +157 -14
  134. package/core/gather/gatherers/stylesheets.js +2 -0
  135. package/core/gather/gatherers/trace-elements.js +2 -1
  136. package/core/gather/gatherers/trace.d.ts +6 -0
  137. package/core/gather/gatherers/trace.js +56 -16
  138. package/core/lib/arbitrary-equality-map.d.ts +14 -3
  139. package/core/lib/arbitrary-equality-map.js +113 -6
  140. package/core/lib/baseline/web-features-data.json +32 -11
  141. package/core/lib/baseline/web-features-metadata.json +1 -1
  142. package/core/lib/bf-cache-strings.js +237 -254
  143. package/core/lib/cdt/Common.d.ts +1 -1
  144. package/core/lib/cdt/Common.js +2 -2
  145. package/core/lib/cdt/Platform.d.ts +7 -7
  146. package/core/lib/cdt/Platform.js +11 -8
  147. package/core/lib/cdt/SDK.d.ts +5 -1
  148. package/core/lib/cdt/SDK.js +4 -2
  149. package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
  150. package/core/lib/cdt/generated/ParsedURL.js +3 -9
  151. package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
  152. package/core/lib/cdt/generated/SourceMap.js +7 -15
  153. package/core/lib/deprecations-strings.d.ts +93 -88
  154. package/core/lib/deprecations-strings.js +20 -14
  155. package/core/lib/i18n/i18n.d.ts +0 -1
  156. package/core/lib/i18n/i18n.js +17 -11
  157. package/core/lib/legacy-javascript/legacy-javascript.d.ts +3 -2
  158. package/core/lib/legacy-javascript/legacy-javascript.js +235 -111
  159. package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
  160. package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
  161. package/core/lib/minification-estimator.js +10 -10
  162. package/core/lib/network-request.d.ts +2 -0
  163. package/core/lib/network-request.js +21 -0
  164. package/core/lib/page-functions.js +2 -5
  165. package/core/lib/sentry.d.ts +1 -0
  166. package/core/lib/sentry.js +3 -0
  167. package/core/lib/trace-engine.d.ts +1 -0
  168. package/core/lib/trace-engine.js +2 -0
  169. package/core/runner.js +9 -3
  170. package/dist/report/bundle.esm.js +10 -10
  171. package/dist/report/flow.js +13 -13
  172. package/dist/report/standalone.js +5 -5
  173. package/flow-report/src/common.tsx +5 -2
  174. package/flow-report/src/i18n/i18n.d.ts +2 -0
  175. package/flow-report/src/summary/summary.tsx +9 -1
  176. package/flow-report/src/util.d.ts +1 -1
  177. package/package.json +62 -98
  178. package/readme.md +13 -18
  179. package/report/generator/file-namer.d.ts +1 -1
  180. package/report/generator/file-namer.js +5 -4
  181. package/report/generator/flow-report-assets.js +1 -3
  182. package/report/generator/report-assets.js +1 -2
  183. package/report/renderer/category-renderer.js +2 -2
  184. package/report/renderer/details-renderer.js +3 -2
  185. package/report/renderer/dom.d.ts +8 -2
  186. package/report/renderer/dom.js +29 -1
  187. package/report/renderer/element-screenshot-renderer.js +3 -0
  188. package/report/renderer/performance-category-renderer.js +3 -1
  189. package/report/renderer/report-renderer.js +4 -0
  190. package/report/renderer/report-utils.d.ts +1 -0
  191. package/report/renderer/report-utils.js +2 -0
  192. package/report/renderer/text-encoding.js +2 -2
  193. package/report/types/augment-dom.d.ts +4 -0
  194. package/shared/esm-utils.d.ts +0 -8
  195. package/shared/esm-utils.js +0 -18
  196. package/shared/localization/format.js +1 -2
  197. package/shared/localization/locales/ar-XB.json +157 -142
  198. package/shared/localization/locales/ar.json +157 -142
  199. package/shared/localization/locales/bg.json +154 -139
  200. package/shared/localization/locales/ca.json +157 -142
  201. package/shared/localization/locales/cs.json +148 -133
  202. package/shared/localization/locales/da.json +153 -138
  203. package/shared/localization/locales/de.json +152 -137
  204. package/shared/localization/locales/el.json +153 -138
  205. package/shared/localization/locales/en-GB.json +160 -145
  206. package/shared/localization/locales/en-US.json +239 -218
  207. package/shared/localization/locales/en-XL.json +239 -218
  208. package/shared/localization/locales/es-419.json +150 -135
  209. package/shared/localization/locales/es.json +149 -134
  210. package/shared/localization/locales/fi.json +151 -136
  211. package/shared/localization/locales/fil.json +154 -139
  212. package/shared/localization/locales/fr.json +150 -135
  213. package/shared/localization/locales/he.json +160 -145
  214. package/shared/localization/locales/hi.json +156 -141
  215. package/shared/localization/locales/hr.json +154 -139
  216. package/shared/localization/locales/hu.json +111 -96
  217. package/shared/localization/locales/id.json +153 -138
  218. package/shared/localization/locales/it.json +153 -138
  219. package/shared/localization/locales/ja.json +150 -135
  220. package/shared/localization/locales/ko.json +42 -27
  221. package/shared/localization/locales/lt.json +152 -137
  222. package/shared/localization/locales/lv.json +93 -78
  223. package/shared/localization/locales/nl.json +149 -134
  224. package/shared/localization/locales/no.json +152 -137
  225. package/shared/localization/locales/pl.json +153 -138
  226. package/shared/localization/locales/pt-PT.json +152 -137
  227. package/shared/localization/locales/pt.json +157 -142
  228. package/shared/localization/locales/ro.json +149 -134
  229. package/shared/localization/locales/ru.json +76 -61
  230. package/shared/localization/locales/sk.json +153 -138
  231. package/shared/localization/locales/sl.json +149 -134
  232. package/shared/localization/locales/sr-Latn.json +151 -136
  233. package/shared/localization/locales/sr.json +151 -136
  234. package/shared/localization/locales/sv.json +152 -137
  235. package/shared/localization/locales/ta.json +156 -141
  236. package/shared/localization/locales/te.json +157 -142
  237. package/shared/localization/locales/th.json +111 -96
  238. package/shared/localization/locales/tr.json +153 -138
  239. package/shared/localization/locales/uk.json +149 -134
  240. package/shared/localization/locales/vi.json +162 -147
  241. package/shared/localization/locales/zh-HK.json +150 -135
  242. package/shared/localization/locales/zh-TW.json +154 -139
  243. package/shared/localization/locales/zh.json +152 -137
  244. package/shared/localization/swap-locale.js +10 -5
  245. package/shared/root.js +1 -3
  246. package/third-party/ard/README.md +19 -10
  247. package/third-party/ard/ard-test.js +189 -266
  248. package/third-party/ard/ard.d.ts +18 -1
  249. package/third-party/ard/ard.js +282 -45
  250. package/third-party/ard/schema-validator.d.ts +14 -5
  251. package/third-party/ard/schema-validator.js +921 -1177
  252. package/third-party/ard/spec/schemas/ard-entry.schema.json +176 -0
  253. package/tsconfig.json +0 -2
  254. package/types/artifacts.d.ts +43 -7
  255. package/types/externs.d.ts +4 -0
  256. package/types/internal/test.d.ts +1 -0
  257. package/build-tracker.config.js +0 -16
  258. package/core/lib/cdt/package.json +0 -4
  259. package/eslint-local-rules.d.cts +0 -9
  260. package/eslint.config.mjs +0 -242
  261. package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
  262. package/third-party/esbuild-plugins-polyfills/LICENSE +0 -3
  263. package/third-party/esbuild-plugins-polyfills/README.md +0 -7
  264. package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.d.ts +0 -9
  265. package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.js +0 -129
  266. package/types/internal/lookup-closest-locale.d.ts +0 -13
@@ -1,7 +1,9 @@
1
+ export type ParsedMediaType = [string, Array<[string, string]>];
1
2
  export type ValidationError = {
2
3
  element: string;
3
4
  message: string;
4
5
  };
6
+ export const ARD_VERSION: "0.91 (f073b1e)";
5
7
  /**
6
8
  * @typedef {{
7
9
  * element: string,
@@ -13,6 +15,8 @@ export class ConformanceTester {
13
15
  errors: ValidationError[];
14
16
  /** @type {ValidationError[]} */
15
17
  warnings: ValidationError[];
18
+ /** @type {ValidationError[]} */
19
+ infos: ValidationError[];
16
20
  /**
17
21
  * @param {string} message
18
22
  * @param {string} [element='Root']
@@ -23,7 +27,15 @@ export class ConformanceTester {
23
27
  * @param {string} [element='Root']
24
28
  */
25
29
  add_warning(message: string, element?: string): void;
26
- /** @param {any} manifest_data */
30
+ /**
31
+ * @param {string} message
32
+ * @param {string} [element='Root']
33
+ */
34
+ add_info(message: string, element?: string): void;
35
+ /**
36
+ * Validate the manifest as an ArdManifest, then every entry as an ArdEntry.
37
+ * @param {any} manifest_data
38
+ */
27
39
  run_json_schema_validation(manifest_data: any): boolean;
28
40
  /**
29
41
  * @param {string} raw_content
@@ -31,4 +43,9 @@ export class ConformanceTester {
31
43
  */
32
44
  validate_manifest(raw_content: string, source_label: string): boolean;
33
45
  }
46
+ /**
47
+ * @param {unknown} media_type
48
+ * @return {['warning'|'info', string]|null}
49
+ */
50
+ export function classify_media_type(media_type: unknown): ["warning" | "info", string] | null;
34
51
  //# sourceMappingURL=ard.d.ts.map
@@ -4,12 +4,222 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ /**
8
+ * @fileoverview JavaScript port of the manifest validation in the ARD conformance test
9
+ * (`conformance/bin/conformance-test`, ARD v0.91). See README.md for the pinned upstream commit
10
+ * and the Lighthouse-specific adaptations.
11
+ */
12
+
7
13
  import log from 'lighthouse-logger';
8
- import validateAiCatalog from './schema-validator.js';
14
+ import {ArdManifest as validateArdManifest, ArdEntry as validateArdEntry} from './schema-validator.js';
15
+
16
+ const ARD_VERSION = '0.91 (f073b1e)';
9
17
 
10
18
  // Strict URN Regex matching urn:air:<publisher>:<namespace>:<agent-name>
11
19
  const URN_REGEX = /^urn:air:([a-zA-Z0-9.-]+)(?::([a-zA-Z0-9._:-]+))?:([a-zA-Z0-9._-]+)$/;
12
20
 
21
+ // RFC 6838 restricted-name for type/subtype plus RFC 9110 parameters.
22
+ const RESTRICTED_NAME = '[0-9A-Za-z][0-9A-Za-z!#$&^_.+\\-]{0,126}';
23
+ const TOKEN = "[!#$%&'*+\\-.^_`|~0-9A-Za-z]+";
24
+ const QUOTED_STRING = '"(?:[\\t !#-\\[\\]-~]|\\\\[\\t !-~])*"';
25
+ const MEDIA_TYPE_REGEX = new RegExp(
26
+ `^(?<type>${RESTRICTED_NAME})/(?<subtype>${RESTRICTED_NAME})` +
27
+ `(?<parameters>(?:[ \\t]*;[ \\t]*${TOKEN}[ \\t]*=[ \\t]*(?:${TOKEN}|${QUOTED_STRING}))*)$`
28
+ );
29
+ const MEDIA_TYPE_PARAMETER_REGEX = new RegExp(
30
+ `[ \\t]*;[ \\t]*(?<name>${TOKEN})[ \\t]*=[ \\t]*` +
31
+ `(?<value>${TOKEN}|${QUOTED_STRING})`,
32
+ 'g'
33
+ );
34
+
35
+ /**
36
+ * @typedef {[string, Array<[string, string]>]} ParsedMediaType
37
+ */
38
+
39
+ /**
40
+ * @param {string} media_type
41
+ * @return {ParsedMediaType|null}
42
+ */
43
+ function parse_media_type(media_type) {
44
+ const match = MEDIA_TYPE_REGEX.exec(media_type);
45
+ if (!match?.groups) {
46
+ return null;
47
+ }
48
+
49
+ const base_media_type =
50
+ `${match.groups.type.toLowerCase()}/${match.groups.subtype.toLowerCase()}`;
51
+ /** @type {Array<[string, string]>} */
52
+ const parameters = [...match.groups.parameters.matchAll(MEDIA_TYPE_PARAMETER_REGEX)]
53
+ .map(parameter => /** @type {[string, string]} */ ([
54
+ /** @type {Record<string, string>} */ (parameter.groups).name.toLowerCase(),
55
+ /** @type {Record<string, string>} */ (parameter.groups).value,
56
+ ]))
57
+ // Python sorts (name, value) tuples by code point.
58
+ .sort((a, b) => {
59
+ if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;
60
+ if (a[1] !== b[1]) return a[1] < b[1] ? -1 : 1;
61
+ return 0;
62
+ });
63
+ const parameter_names = parameters.map(([name]) => name);
64
+ if (parameter_names.length !== new Set(parameter_names).size) {
65
+ return null;
66
+ }
67
+ return [base_media_type, parameters];
68
+ }
69
+
70
+ /** @param {ParsedMediaType} parsed */
71
+ const mediaTypeKey = parsed => JSON.stringify(parsed);
72
+
73
+ const STANDARD_MEDIA_TYPES = [
74
+ "application/ai-catalog+json",
75
+ "application/agent-card+json",
76
+ "application/a2a-agent-card+json",
77
+ "application/mcp-server-card+json",
78
+ "application/agent-skills+json",
79
+ "application/agent-skills+md",
80
+ "application/agent-skills+zip",
81
+ "application/agent-skills+gzip",
82
+ 'text/markdown; profile="urn:air:agent-skills"',
83
+ "application/agent-plugins+zip",
84
+ "application/agent-plugins+gzip",
85
+ "application/ai-registry",
86
+ "application/ai-registry+json",
87
+ ];
88
+ const STANDARD_MEDIA_TYPES_PARSED = STANDARD_MEDIA_TYPES.map(media_type =>
89
+ /** @type {ParsedMediaType} */ (parse_media_type(media_type)));
90
+ const STANDARD_MEDIA_TYPE_KEYS = new Set(STANDARD_MEDIA_TYPES_PARSED.map(mediaTypeKey));
91
+ const STANDARD_BASE_MEDIA_TYPES = new Set(STANDARD_MEDIA_TYPES_PARSED.map(([base]) => base));
92
+ /** @type {Map<string, Map<string, string>>} */
93
+ const STANDARD_PARAMETERS_BY_BASE = new Map(STANDARD_MEDIA_TYPES_PARSED.map(
94
+ ([base, parameters]) => [base, new Map(parameters)]));
95
+ /** @type {Record<string, string>} */
96
+ const DEPRECATED_MEDIA_TYPES = {
97
+ "application/mcp-server+json": "application/mcp-server-card+json",
98
+ };
99
+
100
+ /**
101
+ * Python's `repr()` of the list of standard media types, to keep messages identical to upstream.
102
+ */
103
+ const STANDARD_MEDIA_TYPES_REPR = `[${STANDARD_MEDIA_TYPES.map(t => `'${t}'`).join(', ')}]`;
104
+
105
+ /**
106
+ * Name of the Python type a JSON value would parse to, for messages matching upstream.
107
+ * @param {unknown} value
108
+ * @return {string}
109
+ */
110
+ function python_type_name(value) {
111
+ if (value === null) return 'NoneType';
112
+ if (Array.isArray(value)) return 'list';
113
+ if (typeof value === 'boolean') return 'bool';
114
+ if (typeof value === 'number') return Number.isInteger(value) ? 'int' : 'float';
115
+ if (typeof value === 'object') return 'dict';
116
+ return typeof value;
117
+ }
118
+
119
+ /**
120
+ * @param {unknown} media_type
121
+ * @return {['warning'|'info', string]|null}
122
+ */
123
+ function classify_media_type(media_type) {
124
+ if (typeof media_type !== 'string') {
125
+ return [
126
+ "warning",
127
+ `Media type must be a string; got ${python_type_name(media_type)}.`,
128
+ ];
129
+ }
130
+
131
+ const parsed_media_type = parse_media_type(media_type);
132
+ if (parsed_media_type === null) {
133
+ return [
134
+ "warning",
135
+ `Media type '${media_type}' is not a valid IANA media type. ` +
136
+ "Expected 'type/subtype' with optional parameters.",
137
+ ];
138
+ }
139
+
140
+ const [base_media_type, parameters] = parsed_media_type;
141
+ if (STANDARD_MEDIA_TYPE_KEYS.has(mediaTypeKey(parsed_media_type))) {
142
+ return null;
143
+ }
144
+
145
+ const replacement = DEPRECATED_MEDIA_TYPES[base_media_type];
146
+ if (replacement !== undefined) {
147
+ return [
148
+ "warning",
149
+ `Media type '${media_type}' was renamed by ADR-0008. ` +
150
+ `Use '${replacement}'.`,
151
+ ];
152
+ }
153
+
154
+ const expected_parameters = STANDARD_PARAMETERS_BY_BASE.get(base_media_type);
155
+ if (STANDARD_BASE_MEDIA_TYPES.has(base_media_type) && expected_parameters) {
156
+ const actual_parameters = new Map(parameters);
157
+ const missing_parameters = [...expected_parameters]
158
+ .filter(([name, value]) => actual_parameters.get(name) !== value)
159
+ .map(([name, value]) => `${name}=${value}`);
160
+ const unrecognized_parameters = parameters
161
+ .filter(([name, value]) => expected_parameters.get(name) !== value)
162
+ .map(([name]) => name);
163
+ const differences = [];
164
+ if (missing_parameters.length) {
165
+ differences.push(
166
+ "missing required parameters: " + missing_parameters.join(", ")
167
+ );
168
+ }
169
+ if (unrecognized_parameters.length) {
170
+ differences.push(
171
+ "unrecognized parameters: " + unrecognized_parameters.join(", ")
172
+ );
173
+ }
174
+ return [
175
+ "warning",
176
+ `Media type '${media_type}' is based on standard discovery type ` +
177
+ `'${base_media_type}' but has ` +
178
+ `${differences.join('; ')}.`,
179
+ ];
180
+ }
181
+
182
+ if (base_media_type.startsWith("application/")) {
183
+ return [
184
+ "info",
185
+ `Media type '${media_type}' is a valid application extension media type. ` +
186
+ "ARD permits extension types without core registration.",
187
+ ];
188
+ }
189
+
190
+ return [
191
+ "warning",
192
+ `Media type '${media_type}' is not one of standard discovery types: ` +
193
+ `${STANDARD_MEDIA_TYPES_REPR}.`,
194
+ ];
195
+ }
196
+
197
+ /**
198
+ * @param {unknown} value
199
+ * @return {value is Record<string, any>}
200
+ */
201
+ function is_object(value) {
202
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
203
+ }
204
+
205
+ // Errors reported inside a oneOf/anyOf branch or a `not` are restated by the combinator itself.
206
+ const COMBINATOR_BRANCH_REGEX = /\/(?:oneOf|anyOf)\/\d+(?:\/|$)|\/not(?:\/|$)/;
207
+
208
+ /**
209
+ * Formats the most relevant Ajv error as `<message> at path '<path>'`.
210
+ * @param {Array<any>|null|undefined} errors
211
+ * @return {string}
212
+ */
213
+ function format_schema_error(errors) {
214
+ const e = errors?.find(err => !COMBINATOR_BRANCH_REGEX.test(err.schemaPath)) || errors?.[0];
215
+ const propertyPath = e?.instancePath ? e.instancePath.replace(/^\//, '').replace(/\//g, '.') : 'root';
216
+ let msg = e?.message || 'unknown schema error';
217
+ if (e?.keyword === 'required') {
218
+ msg = `'${e.params.missingProperty}' is a required property`;
219
+ }
220
+ return `${msg} at path '${propertyPath}'`;
221
+ }
222
+
13
223
  /**
14
224
  * @typedef {{
15
225
  * element: string,
@@ -23,6 +233,8 @@ class ConformanceTester {
23
233
  this.errors = [];
24
234
  /** @type {ValidationError[]} */
25
235
  this.warnings = [];
236
+ /** @type {ValidationError[]} */
237
+ this.infos = [];
26
238
  }
27
239
 
28
240
  /**
@@ -43,29 +255,53 @@ class ConformanceTester {
43
255
  log.verbose('ARD', `Validation Warning [${element}]: ${message}`);
44
256
  }
45
257
 
46
- /** @param {any} manifest_data */
258
+ /**
259
+ * @param {string} message
260
+ * @param {string} [element='Root']
261
+ */
262
+ add_info(message, element = 'Root') {
263
+ this.infos.push({element, message});
264
+ log.verbose('ARD', `Validation Info [${element}]: ${message}`);
265
+ }
266
+
267
+ /**
268
+ * Validate the manifest as an ArdManifest, then every entry as an ArdEntry.
269
+ * @param {any} manifest_data
270
+ */
47
271
  run_json_schema_validation(manifest_data) {
272
+ let ok = true;
48
273
  try {
49
- const valid = validateAiCatalog(manifest_data);
50
-
51
- if (valid) {
52
- log.verbose('ARD', 'Strict JSON Schema validation passed.');
53
- return true;
274
+ if (validateArdManifest(manifest_data)) {
275
+ log.verbose('ARD', 'Manifest validates against ArdManifest.');
54
276
  } else {
55
- const errors = /** @type {any} */ (validateAiCatalog).errors;
56
- const e = errors?.[0];
57
- const propertyPath = e?.instancePath ? e.instancePath.replace(/^\//, '').replace(/\//g, '.') : 'root';
58
- let msg = e?.message || 'unknown schema error';
59
- if (e?.keyword === 'required') {
60
- msg = `'${e.params.missingProperty}' is a required property`;
61
- }
62
- this.add_error(`JSON Schema Validation Failed: ${msg} at path '${propertyPath}'`, 'Root');
63
- return false;
277
+ const errors = /** @type {any} */ (validateArdManifest).errors;
278
+ this.add_error(`ArdManifest validation failed: ${format_schema_error(errors)}`, 'Root');
279
+ ok = false;
64
280
  }
65
281
  } catch (e) {
66
282
  this.add_warning(`Failed to run JSON Schema validator: ${e}`, 'Root');
67
283
  return true;
68
284
  }
285
+
286
+ const entries = is_object(manifest_data) ? manifest_data["entries"] : undefined;
287
+ if (Array.isArray(entries)) {
288
+ let bad = 0;
289
+ entries.forEach((entry, idx) => {
290
+ const label = is_object(entry) ?
291
+ String(entry["displayName"] || entry["identifier"] || `Entry #${idx}`) :
292
+ `Entry #${idx}`;
293
+ if (!validateArdEntry(entry)) {
294
+ const errors = /** @type {any} */ (validateArdEntry).errors;
295
+ this.add_error(`ArdEntry validation failed: ${format_schema_error(errors)}`, label);
296
+ bad += 1;
297
+ ok = false;
298
+ }
299
+ });
300
+ if (!bad) {
301
+ log.verbose('ARD', `All ${entries.length} entries validate against ArdEntry.`);
302
+ }
303
+ }
304
+ return ok;
69
305
  }
70
306
 
71
307
  /**
@@ -91,14 +327,18 @@ class ConformanceTester {
91
327
  // 3. Custom Semantic and Protocol-Specific Validation
92
328
  log.verbose('ARD', 'Running custom semantic checks...');
93
329
 
94
- const spec_ver = data["specVersion"];
95
- if (!spec_ver) {
96
- this.add_error("Missing required 'specVersion' root property.", 'Root');
97
- } else if (spec_ver !== "1.0") {
98
- this.add_warning(`Unrecognized 'specVersion': ${spec_ver}. Expected '1.0'.`, 'Root');
330
+ // Upstream assumes a JSON object here; treat anything else as an empty root so the
331
+ // schema error above is accompanied by the missing 'entries' error instead of a crash.
332
+ const root = is_object(data) ? data : {};
333
+
334
+ // ARD requires only 'entries' at the top level (§5.1). Other members are
335
+ // transport-defined and ignored, so their presence is noted, never an error.
336
+ const extra_roots = Object.keys(root).filter(k => k !== "entries");
337
+ if (extra_roots.length) {
338
+ log.verbose('ARD', `Top-level members ignored by ARD (transport-defined): ${extra_roots.sort().join(', ')}.`);
99
339
  }
100
340
 
101
- const entries = data["entries"];
341
+ const entries = root["entries"];
102
342
  if (entries === undefined || entries === null) {
103
343
  this.add_error("Missing required 'entries' array.", 'Root');
104
344
  return false;
@@ -108,8 +348,10 @@ class ConformanceTester {
108
348
  }
109
349
 
110
350
  log.verbose('ARD', `Found ${entries.length} entries to validate.`);
111
- entries.forEach((entry, idx) => {
112
- const label = entry["displayName"] || entry["identifier"] || `Entry #${idx}`;
351
+ entries.forEach((raw_entry, idx) => {
352
+ // Upstream assumes each entry is a JSON object; treat anything else as empty.
353
+ const entry = is_object(raw_entry) ? raw_entry : {};
354
+ const label = String(entry["displayName"] || entry["identifier"] || `Entry #${idx}`);
113
355
 
114
356
  // Required properties
115
357
  const ident = entry["identifier"];
@@ -136,35 +378,30 @@ class ConformanceTester {
136
378
  if (!media_type) {
137
379
  this.add_error("Missing required 'type' (mediaType).", label);
138
380
  } else {
139
- const valid_types = [
140
- "application/ai-catalog+json",
141
- "application/agent-card+json",
142
- "application/a2a-agent-card+json",
143
- "application/mcp-server-card+json",
144
- "application/agent-skills+zip",
145
- "application/agent-skills+gzip",
146
- "text/markdown; profile=\"urn:air:agent-skills\"",
147
- "application/ai-registry",
148
- "application/ai-registry+json"
149
- ];
150
- if (!valid_types.includes(media_type)) {
151
- this.add_warning(`Media type '${media_type}' is not one of standard discovery types: ${valid_types}.`, label);
381
+ const diagnostic = classify_media_type(media_type);
382
+ if (diagnostic !== null) {
383
+ const [severity, message] = diagnostic;
384
+ if (severity === "info") {
385
+ this.add_info(message, label);
386
+ } else {
387
+ this.add_warning(message, label);
388
+ }
152
389
  }
153
390
  }
154
391
 
155
392
  // Strict Value-or-Reference checks
156
- const has_url = entry["url"] !== undefined;
157
- const has_data = entry["data"] !== undefined;
393
+ const has_url = "url" in entry;
394
+ const has_data = "data" in entry;
158
395
  if (has_url && has_data) {
159
396
  this.add_error("Constraint violation: both 'url' and 'data' are provided. MUST provide exactly one.", label);
160
397
  } else if (!has_url && !has_data) {
161
398
  this.add_error("Constraint violation: neither 'url' nor 'data' is provided. MUST provide exactly one.", label);
162
399
  }
163
400
 
164
- // Custom constraints for representativeQueries
401
+ // Custom constraints for representativeQueries (§4.2, §D.2)
165
402
  const queries = entry["representativeQueries"];
166
403
  if (queries === undefined || queries === null) {
167
- this.add_warning("Missing 'representativeQueries'. 2 to 5 queries are recommended for vector index embedding.", label);
404
+ this.add_warning("No 'representativeQueries'. The semantic index is built from this term, so the entry will not be found by search — it is a valid catalog entry but not a discoverable ARD entry.", label);
168
405
  } else if (!Array.isArray(queries)) {
169
406
  this.add_error("'representativeQueries' must be an array of strings.", label);
170
407
  } else {
@@ -181,7 +418,7 @@ class ConformanceTester {
181
418
  // Progressive trust checks
182
419
  const trust = entry["trustManifest"];
183
420
  if (trust !== undefined && trust !== null) {
184
- if (typeof trust !== 'object' || Array.isArray(trust)) {
421
+ if (!is_object(trust)) {
185
422
  this.add_error("'trustManifest' must be a JSON object.", label);
186
423
  } else {
187
424
  const trust_id = trust["identity"];
@@ -193,12 +430,12 @@ class ConformanceTester {
193
430
  });
194
431
 
195
432
  // Top-level deprecated property check
196
- if ("collections" in data) {
197
- this.add_error("Deprecated field check: Found 'collections' array at root. Top-level collections were REMOVED in ADR-0003. Catalog hierarchies MUST be modeled inside 'entries' using 'type: application/ai-catalog+json'.", 'Root');
433
+ if ("collections" in root) {
434
+ this.add_warning("Found 'collections' at root. Top-level collections were removed; ARD ignores unrecognized top-level members, so this does not invalidate the manifest, but hierarchies should be modeled inside 'entries'.", 'Root');
198
435
  }
199
436
 
200
437
  return this.errors.length === 0;
201
438
  }
202
439
  }
203
440
 
204
- export { ConformanceTester };
441
+ export { ARD_VERSION, ConformanceTester, classify_media_type };
@@ -1,20 +1,29 @@
1
- export function validate(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
1
+ export function ArdManifest(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
2
2
  instancePath?: string | undefined;
3
3
  rootData?: any;
4
4
  dynamicAnchors?: {} | undefined;
5
5
  }): boolean;
6
- export namespace validate {
6
+ export namespace ArdManifest {
7
7
  namespace evaluated {
8
8
  let props: boolean;
9
9
  let dynamicProps: boolean;
10
10
  let dynamicItems: boolean;
11
11
  }
12
12
  }
13
- export default validate20;
14
- declare function validate20(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
13
+ export function ArdEntry(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
15
14
  instancePath?: string | undefined;
16
15
  rootData?: any;
17
16
  dynamicAnchors?: {} | undefined;
18
17
  }): boolean;
19
- declare namespace validate20 { }
18
+ export namespace ArdEntry {
19
+ export namespace evaluated_1 {
20
+ let props_1: boolean;
21
+ export { props_1 as props };
22
+ let dynamicProps_1: boolean;
23
+ export { dynamicProps_1 as dynamicProps };
24
+ let dynamicItems_1: boolean;
25
+ export { dynamicItems_1 as dynamicItems };
26
+ }
27
+ export { evaluated_1 as evaluated };
28
+ }
20
29
  //# sourceMappingURL=schema-validator.d.ts.map