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.
- package/.agents/skills/update-ard-port/SKILL.md +171 -0
- package/.agents/skills/update-trace-engine/SKILL.md +154 -0
- package/cli/bin.d.ts +2 -2
- package/cli/bin.js +5 -4
- package/cli/cli-flags.d.ts +16 -1
- package/cli/cli-flags.js +53 -2
- package/cli/printer.js +5 -8
- package/cli/run.d.ts +5 -5
- package/cli/run.js +97 -47
- package/cli/sentry-prompt.js +3 -4
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +1 -1
- package/cli/test/smokehouse/config/exclusions.js +3 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
- package/cli/test/smokehouse/report-assert-test.js +65 -0
- package/cli/test/smokehouse/report-assert.js +24 -39
- package/cli/test/smokehouse/smokehouse-test.d.ts +2 -0
- package/cli/test/smokehouse/smokehouse-test.js +120 -0
- package/cli/test/smokehouse/smokehouse.js +37 -15
- package/cli/test/smokehouse/version-check-test.js +5 -0
- package/cli/test/smokehouse/version-check.d.ts +1 -1
- package/cli/test/smokehouse/version-check.js +1 -1
- package/commitlint.config.js +1 -1
- package/core/audits/accessibility/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- package/core/audits/accessibility/aria-conditional-attr.js +1 -1
- package/core/audits/accessibility/aria-deprecated-role.js +1 -1
- package/core/audits/accessibility/aria-dialog-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
- package/core/audits/accessibility/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-text.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/autocomplete-valid.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/empty-heading.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
- package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/image-redundant-alt.js +1 -1
- package/core/audits/accessibility/input-button-name.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/landmark-one-main.js +1 -1
- package/core/audits/accessibility/link-in-text-block.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/presentation-role-conflict.js +1 -1
- package/core/audits/accessibility/select-name.js +1 -1
- package/core/audits/accessibility/skip-link.js +1 -1
- package/core/audits/accessibility/svg-img-alt.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/table-duplicate-name.js +1 -1
- package/core/audits/accessibility/table-fake-caption.js +1 -1
- package/core/audits/accessibility/target-size.js +1 -1
- package/core/audits/accessibility/td-has-header.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -1
- package/core/audits/agentic/ard-schema.d.ts +13 -0
- package/core/audits/agentic/ard-schema.js +81 -33
- package/core/audits/agentic/llms-txt.js +5 -2
- package/core/audits/baseline.d.ts +29 -7
- package/core/audits/baseline.js +19 -6
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
- package/core/audits/insights/insight-audit.d.ts +9 -0
- package/core/audits/insights/insight-audit.js +35 -28
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/screenshot-thumbnails.d.ts +0 -13
- package/core/audits/screenshot-thumbnails.js +0 -45
- package/core/audits/script-treemap-data.js +26 -6
- package/core/audits/seo/robots-txt.js +3 -8
- package/core/audits/unsized-images.d.ts +0 -7
- package/core/audits/unsized-images.js +7 -26
- package/core/computed/lcp-image-record.d.ts +2 -23
- package/core/computed/lcp-image-record.js +3 -67
- package/core/computed/lcp-network-record.d.ts +24 -0
- package/core/computed/lcp-network-record.js +74 -0
- package/core/computed/metrics/lantern-metric.js +1 -1
- package/core/computed/metrics/lcp-breakdown.js +6 -6
- package/core/computed/metrics/time-to-first-byte.js +5 -3
- package/core/computed/navigation-insights.js +3 -2
- package/core/computed/page-dependency-graph.js +55 -5
- package/core/computed/speedline.js +20 -5
- package/core/computed/trace-engine-result.js +25 -5
- package/core/computed/unused-css.js +15 -2
- package/core/config/config-helpers.d.ts +5 -0
- package/core/config/config-helpers.js +8 -28
- package/core/config/config.js +1 -2
- package/core/config/constants.d.ts +2 -28
- package/core/config/lr-desktop-config.js +13 -0
- package/core/config/lr-mobile-config.js +13 -0
- package/core/gather/base-artifacts.js +7 -1
- package/core/gather/driver/storage.js +3 -2
- package/core/gather/gatherers/agentic/ard.d.ts +59 -4
- package/core/gather/gatherers/agentic/ard.js +218 -56
- package/core/gather/gatherers/full-page-screenshot.js +2 -2
- package/core/gather/gatherers/image-elements.d.ts +7 -0
- package/core/gather/gatherers/image-elements.js +75 -24
- package/core/gather/gatherers/inspector-issues.js +2 -0
- package/core/gather/gatherers/js-usage.js +2 -0
- package/core/gather/gatherers/scripts.d.ts +16 -3
- package/core/gather/gatherers/scripts.js +157 -14
- package/core/gather/gatherers/stylesheets.js +2 -0
- package/core/gather/gatherers/trace-elements.js +2 -1
- package/core/gather/gatherers/trace.d.ts +6 -0
- package/core/gather/gatherers/trace.js +56 -16
- package/core/lib/arbitrary-equality-map.d.ts +14 -3
- package/core/lib/arbitrary-equality-map.js +113 -6
- package/core/lib/baseline/web-features-data.json +32 -11
- package/core/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/bf-cache-strings.js +237 -254
- package/core/lib/cdt/Common.d.ts +1 -1
- package/core/lib/cdt/Common.js +2 -2
- package/core/lib/cdt/Platform.d.ts +7 -7
- package/core/lib/cdt/Platform.js +11 -8
- package/core/lib/cdt/SDK.d.ts +5 -1
- package/core/lib/cdt/SDK.js +4 -2
- package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
- package/core/lib/cdt/generated/ParsedURL.js +3 -9
- package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
- package/core/lib/cdt/generated/SourceMap.js +7 -15
- package/core/lib/deprecations-strings.d.ts +93 -88
- package/core/lib/deprecations-strings.js +20 -14
- package/core/lib/i18n/i18n.d.ts +0 -1
- package/core/lib/i18n/i18n.js +17 -11
- package/core/lib/legacy-javascript/legacy-javascript.d.ts +3 -2
- package/core/lib/legacy-javascript/legacy-javascript.js +235 -111
- package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
- package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
- package/core/lib/minification-estimator.js +10 -10
- package/core/lib/network-request.d.ts +2 -0
- package/core/lib/network-request.js +21 -0
- package/core/lib/page-functions.js +2 -5
- package/core/lib/sentry.d.ts +1 -0
- package/core/lib/sentry.js +3 -0
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/core/runner.js +9 -3
- package/dist/report/bundle.esm.js +10 -10
- package/dist/report/flow.js +13 -13
- package/dist/report/standalone.js +5 -5
- package/flow-report/src/common.tsx +5 -2
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/flow-report/src/summary/summary.tsx +9 -1
- package/flow-report/src/util.d.ts +1 -1
- package/package.json +62 -98
- package/readme.md +13 -18
- package/report/generator/file-namer.d.ts +1 -1
- package/report/generator/file-namer.js +5 -4
- package/report/generator/flow-report-assets.js +1 -3
- package/report/generator/report-assets.js +1 -2
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/details-renderer.js +3 -2
- package/report/renderer/dom.d.ts +8 -2
- package/report/renderer/dom.js +29 -1
- package/report/renderer/element-screenshot-renderer.js +3 -0
- package/report/renderer/performance-category-renderer.js +3 -1
- package/report/renderer/report-renderer.js +4 -0
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/text-encoding.js +2 -2
- package/report/types/augment-dom.d.ts +4 -0
- package/shared/esm-utils.d.ts +0 -8
- package/shared/esm-utils.js +0 -18
- package/shared/localization/format.js +1 -2
- package/shared/localization/locales/ar-XB.json +157 -142
- package/shared/localization/locales/ar.json +157 -142
- package/shared/localization/locales/bg.json +154 -139
- package/shared/localization/locales/ca.json +157 -142
- package/shared/localization/locales/cs.json +148 -133
- package/shared/localization/locales/da.json +153 -138
- package/shared/localization/locales/de.json +152 -137
- package/shared/localization/locales/el.json +153 -138
- package/shared/localization/locales/en-GB.json +160 -145
- package/shared/localization/locales/en-US.json +239 -218
- package/shared/localization/locales/en-XL.json +239 -218
- package/shared/localization/locales/es-419.json +150 -135
- package/shared/localization/locales/es.json +149 -134
- package/shared/localization/locales/fi.json +151 -136
- package/shared/localization/locales/fil.json +154 -139
- package/shared/localization/locales/fr.json +150 -135
- package/shared/localization/locales/he.json +160 -145
- package/shared/localization/locales/hi.json +156 -141
- package/shared/localization/locales/hr.json +154 -139
- package/shared/localization/locales/hu.json +111 -96
- package/shared/localization/locales/id.json +153 -138
- package/shared/localization/locales/it.json +153 -138
- package/shared/localization/locales/ja.json +150 -135
- package/shared/localization/locales/ko.json +42 -27
- package/shared/localization/locales/lt.json +152 -137
- package/shared/localization/locales/lv.json +93 -78
- package/shared/localization/locales/nl.json +149 -134
- package/shared/localization/locales/no.json +152 -137
- package/shared/localization/locales/pl.json +153 -138
- package/shared/localization/locales/pt-PT.json +152 -137
- package/shared/localization/locales/pt.json +157 -142
- package/shared/localization/locales/ro.json +149 -134
- package/shared/localization/locales/ru.json +76 -61
- package/shared/localization/locales/sk.json +153 -138
- package/shared/localization/locales/sl.json +149 -134
- package/shared/localization/locales/sr-Latn.json +151 -136
- package/shared/localization/locales/sr.json +151 -136
- package/shared/localization/locales/sv.json +152 -137
- package/shared/localization/locales/ta.json +156 -141
- package/shared/localization/locales/te.json +157 -142
- package/shared/localization/locales/th.json +111 -96
- package/shared/localization/locales/tr.json +153 -138
- package/shared/localization/locales/uk.json +149 -134
- package/shared/localization/locales/vi.json +162 -147
- package/shared/localization/locales/zh-HK.json +150 -135
- package/shared/localization/locales/zh-TW.json +154 -139
- package/shared/localization/locales/zh.json +152 -137
- package/shared/localization/swap-locale.js +10 -5
- package/shared/root.js +1 -3
- package/third-party/ard/README.md +19 -10
- package/third-party/ard/ard-test.js +189 -266
- package/third-party/ard/ard.d.ts +18 -1
- package/third-party/ard/ard.js +282 -45
- package/third-party/ard/schema-validator.d.ts +14 -5
- package/third-party/ard/schema-validator.js +921 -1177
- package/third-party/ard/spec/schemas/ard-entry.schema.json +176 -0
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +43 -7
- package/types/externs.d.ts +4 -0
- package/types/internal/test.d.ts +1 -0
- package/build-tracker.config.js +0 -16
- package/core/lib/cdt/package.json +0 -4
- package/eslint-local-rules.d.cts +0 -9
- package/eslint.config.mjs +0 -242
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
- package/third-party/esbuild-plugins-polyfills/LICENSE +0 -3
- package/third-party/esbuild-plugins-polyfills/README.md +0 -7
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.d.ts +0 -9
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.js +0 -129
- package/types/internal/lookup-closest-locale.d.ts +0 -13
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {set} from 'lodash-es';
|
|
8
|
-
import {get} from 'lodash-es';
|
|
9
|
-
|
|
10
7
|
import * as format from './format.js';
|
|
11
8
|
|
|
12
9
|
/**
|
|
@@ -70,9 +67,17 @@ function swapLocale(lhr, requestedLocale) {
|
|
|
70
67
|
values = instance.values;
|
|
71
68
|
}
|
|
72
69
|
|
|
70
|
+
const keys = path.replace(/\[([^\]]+)\]/g, '.$1').replace(/^\./, '').split('.');
|
|
71
|
+
/** @type {any} */
|
|
72
|
+
let parent = lhr;
|
|
73
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
74
|
+
parent = parent?.[keys[i]];
|
|
75
|
+
}
|
|
76
|
+
const lastKey = keys[keys.length - 1];
|
|
77
|
+
|
|
73
78
|
// If the path isn't valid or the value isn't a string, there's no point in trying to replace it.
|
|
74
79
|
/** @type {unknown} */
|
|
75
|
-
const originalString =
|
|
80
|
+
const originalString = parent?.[lastKey];
|
|
76
81
|
if (typeof originalString !== 'string') {
|
|
77
82
|
continue;
|
|
78
83
|
}
|
|
@@ -96,7 +101,7 @@ function swapLocale(lhr, requestedLocale) {
|
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
// Write string back into the LHR.
|
|
99
|
-
|
|
104
|
+
parent[lastKey] = relocalizedString;
|
|
100
105
|
}
|
|
101
106
|
}
|
|
102
107
|
|
package/shared/root.js
CHANGED
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const LH_ROOT = path.dirname(getModuleDirectory(import.meta));
|
|
10
|
+
const LH_ROOT = path.dirname(import.meta.dirname);
|
|
13
11
|
const pkg = JSON.parse(fs.readFileSync(`${LH_ROOT}/package.json`, 'utf-8'));
|
|
14
12
|
const lighthouseVersion = pkg.version;
|
|
15
13
|
|
|
@@ -5,23 +5,32 @@ This is a JavaScript port of the Agentic Resource Discovery (ARD) Conformance Te
|
|
|
5
5
|
The original Python script is located in the `ards-project/ard-spec` repository:
|
|
6
6
|
- **Upstream Repository**: https://github.com/ards-project/ard-spec
|
|
7
7
|
- **Source Script**: `conformance/bin/conformance-test`
|
|
8
|
-
- **Schema**: `spec/schemas/
|
|
9
|
-
- **Pinned Commit SHA**: `
|
|
8
|
+
- **Schema**: `spec/schemas/ard-entry.schema.json` (`$defs/ArdManifest` and `$defs/ArdEntry`)
|
|
9
|
+
- **Pinned Commit SHA**: `f073b1ea9b01a5f068d0b0986426f2ea49fa30b2`
|
|
10
|
+
- **Reviewed Upstream Files** (SHA-256 of the upstream content the port was last verified against; maintained by `yarn update:ard-spec --ack-port`, do not edit by hand):
|
|
11
|
+
- `conformance/bin/conformance-test`: `428b6cc17e8bb76c796c79b30953a02d70e92a6a9da8d0ef43027f7e968c1c7c`
|
|
12
|
+
- `spec/ard.md`: `6d11107b60ff962d636c2bb66bf242d2c5d4f47eb0fbbec3b498deb3db771b78`
|
|
10
13
|
|
|
11
14
|
## Modifications for Lighthouse:
|
|
12
15
|
While the validation rules and test suite maintain 1:1 parity with the reference suite, the following adaptations were made for Lighthouse integration:
|
|
13
|
-
- **Structured Error Return:** `ConformanceTester` stores structured objects (`{ element, message }`) in `errors` and `
|
|
14
|
-
- **Precompiled Standalone Validator (`schema-validator.js`):** Uses `build/build-ard-schema.js` (`yarn build-ard-schema`) to precompile `
|
|
16
|
+
- **Structured Error Return:** `ConformanceTester` stores structured objects (`{ element, message }`) in `errors`, `warnings` and `infos` instead of formatted ANSI CLI strings. The `[label]` prefix upstream puts on entry messages is reported as `element` instead.
|
|
17
|
+
- **Precompiled Standalone Validator (`schema-validator.js`):** Uses `build/build-ard-schema.js` (`yarn build-ard-schema`) to precompile the `ArdManifest` and `ArdEntry` definitions of `ard-entry.schema.json` into a pure, standalone JavaScript module (`schema-validator.js`) ahead of time using `Ajv2020` and `ajv/dist/standalone`. This eliminates runtime dynamic compilation (`new Function()` / `eval()`), preventing Content Security Policy (CSP) `unsafe-eval` violations when running inside Chrome DevTools frontend, avoiding runtime `fs.readFileSync` calls in browser/bundled contexts, and keeping `Ajv` out of the client bundle.
|
|
15
18
|
- **ESM Format Validation:** Imports `uri` and `date-time` format validators statically from `ajv-formats/dist/formats.js` rather than bundling the full `ajv-formats` dynamic plugin.
|
|
19
|
+
- **Schema Error Messages:** Ajv reports different messages than Python's `jsonschema`. The first error not nested inside a `oneOf`/`anyOf` branch is reported, with `'x' is a required property` phrasing kept for `required` errors.
|
|
20
|
+
- **Non-object Input:** Upstream assumes the manifest root and each entry are JSON objects. The port treats anything else as empty, so it reports errors instead of throwing.
|
|
16
21
|
- **Lighthouse Logger:** Replaced raw `console.log` with `lighthouse-logger`.
|
|
17
22
|
|
|
23
|
+
Publisher resolution (upstream's `resolve_publisher`, spec §5.1) is implemented by the gatherer in `core/gather/gatherers/agentic/ard.js`, and the resulting legacy-location warning by the `ard-schema` audit. `infos` are not surfaced in the audit.
|
|
24
|
+
|
|
18
25
|
## Updating Conformance Script
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
`yarn check:ard-spec` (`core/scripts/update-ard-spec.js --check`) compares upstream `main` against what Lighthouse has: the vendored schema must match byte-for-byte, and the hand-ported files must match the **Reviewed Upstream Files** hashes above. It runs weekly in CI (`.github/workflows/cron-weekly.yml`) and during dependency upgrades (`core/scripts/upgrade-deps.sh`). Neither of those can mark upstream changes as handled; only `--ack-port` does.
|
|
21
28
|
|
|
22
|
-
When
|
|
23
|
-
1. Run `yarn update:ard-spec
|
|
24
|
-
2.
|
|
25
|
-
3.
|
|
26
|
-
4.
|
|
29
|
+
When it reports changes (the `update-ard-port` agent skill walks through this in detail):
|
|
30
|
+
1. Run `yarn update:ard-spec`. This syncs `ard-entry.schema.json` verbatim and prints the upstream `conformance-test` diff. If a hand-ported file changed, it exits 1 **without** bumping the pinned SHA.
|
|
31
|
+
2. Port `conformance/bin/conformance-test` changes into `third-party/ard/ard.js` (keep rules, severities and message text 1:1; see modifications above) and update `third-party/ard/ard-test.js`.
|
|
32
|
+
3. If `spec/ard.md` changed, check §5.1 (Discovery Mechanisms) against `core/gather/gatherers/agentic/ard.js`.
|
|
33
|
+
4. If the schema changed, run `yarn build-ard-schema` to regenerate `schema-validator.js`.
|
|
34
|
+
5. Verify: `yarn mocha third-party/ard/ard-test.js core/test/gather/gatherers/agentic/ard-test.js core/test/audits/agentic/ard-schema-test.js` and `yarn smoke ard ardInvalid`.
|
|
35
|
+
6. Run `yarn update:ard-spec --ack-port` to record the reviewed hashes and bump the pinned SHA. `yarn check:ard-spec` should now pass.
|
|
27
36
|
|
|
@@ -7,352 +7,275 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* @fileoverview Unit tests for ARD Schema validation logic.
|
|
9
9
|
* Tests mirror the validation rules from
|
|
10
|
-
* ards-project/ard-spec/conformance/bin/conformance-test (validate_manifest).
|
|
10
|
+
* ards-project/ard-spec/conformance/bin/conformance-test (validate_manifest, ARD v0.91).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {ConformanceTester} from './ard.js';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @param {Record<string, any>} overrides
|
|
17
|
+
*/
|
|
18
|
+
function entry(overrides = {}) {
|
|
19
|
+
return {
|
|
20
|
+
identifier: 'urn:air:google:search:web-search',
|
|
21
|
+
displayName: 'Web Search API',
|
|
22
|
+
type: 'application/mcp-server-card+json',
|
|
23
|
+
url: 'https://example.com/mcp.json',
|
|
24
|
+
representativeQueries: ['search web', 'find articles'],
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Array<{message: string}>} list
|
|
31
|
+
* @param {string} text
|
|
32
|
+
*/
|
|
33
|
+
function has(list, text) {
|
|
34
|
+
return list.some(i => i.message.includes(text));
|
|
35
|
+
}
|
|
36
|
+
|
|
15
37
|
describe('ARD Schema validation (ported from ard-spec)', () => {
|
|
16
38
|
/** @type {ConformanceTester} */
|
|
17
39
|
let tester;
|
|
18
|
-
|
|
40
|
+
|
|
19
41
|
beforeEach(() => {
|
|
20
42
|
tester = new ConformanceTester();
|
|
21
43
|
});
|
|
22
44
|
|
|
45
|
+
it('fails with Error when manifest is malformed JSON', () => {
|
|
46
|
+
tester.validate_manifest('{ invalid json', 'test');
|
|
47
|
+
expect(has(tester.errors, 'Malformed JSON in manifest')).toEqual(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
23
50
|
it('fails with Error when manifest root is not an object', () => {
|
|
24
51
|
tester.validate_manifest(JSON.stringify('just a string'), 'test');
|
|
25
|
-
expect(
|
|
26
|
-
|
|
27
|
-
|
|
52
|
+
expect(has(tester.errors, 'ArdManifest validation failed')).toEqual(true);
|
|
53
|
+
expect(has(tester.errors, 'Missing required \'entries\' array')).toEqual(true);
|
|
54
|
+
|
|
55
|
+
tester = new ConformanceTester();
|
|
56
|
+
tester.validate_manifest('null', 'test');
|
|
57
|
+
expect(has(tester.errors, 'Missing required \'entries\' array')).toEqual(true);
|
|
28
58
|
});
|
|
29
59
|
|
|
30
|
-
it('
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
tester.
|
|
35
|
-
expect(
|
|
36
|
-
tester.errors.some(i => i.message.includes('Missing required \'specVersion\''))
|
|
37
|
-
).toEqual(true);
|
|
60
|
+
it('passes when specVersion is absent (ARD requires only entries)', () => {
|
|
61
|
+
const passed = tester.validate_manifest(JSON.stringify({entries: [entry()]}), 'test');
|
|
62
|
+
expect(passed).toEqual(true);
|
|
63
|
+
expect(tester.errors).toEqual([]);
|
|
64
|
+
expect(tester.warnings).toEqual([]);
|
|
38
65
|
});
|
|
39
66
|
|
|
40
|
-
it('
|
|
67
|
+
it('ignores specVersion and other unrecognized top-level members', () => {
|
|
41
68
|
const manifest = {
|
|
42
69
|
specVersion: '2.0',
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
identifier: 'urn:air:google:search:web-search',
|
|
46
|
-
displayName: 'Web Search',
|
|
47
|
-
type: 'application/mcp-server-card+json',
|
|
48
|
-
url: 'https://example.com/api',
|
|
49
|
-
representativeQueries: ['query 1', 'query 2'],
|
|
50
|
-
},
|
|
51
|
-
],
|
|
70
|
+
host: {displayName: 'Example'},
|
|
71
|
+
entries: [entry()],
|
|
52
72
|
};
|
|
53
|
-
tester.validate_manifest(JSON.stringify(manifest), 'test');
|
|
54
|
-
expect(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
58
|
-
).toEqual(true);
|
|
73
|
+
const passed = tester.validate_manifest(JSON.stringify(manifest), 'test');
|
|
74
|
+
expect(passed).toEqual(true);
|
|
75
|
+
expect(tester.errors).toEqual([]);
|
|
76
|
+
expect(tester.warnings).toEqual([]);
|
|
59
77
|
});
|
|
60
78
|
|
|
61
|
-
it('
|
|
79
|
+
it('warns, but does not fail, when root contains deprecated collections', () => {
|
|
62
80
|
const manifest = {
|
|
63
81
|
specVersion: '1.0',
|
|
64
82
|
collections: [],
|
|
65
|
-
entries: [],
|
|
83
|
+
entries: [entry()],
|
|
66
84
|
};
|
|
67
|
-
tester.validate_manifest(JSON.stringify(manifest), 'test');
|
|
68
|
-
expect(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
const passed = tester.validate_manifest(JSON.stringify(manifest), 'test');
|
|
86
|
+
expect(passed).toEqual(true);
|
|
87
|
+
expect(tester.errors).toEqual([]);
|
|
88
|
+
expect(tester.warnings).toEqual([
|
|
89
|
+
{
|
|
90
|
+
element: 'Root',
|
|
91
|
+
message: 'Found \'collections\' at root. Top-level collections were removed; ARD ' +
|
|
92
|
+
'ignores unrecognized top-level members, so this does not invalidate the manifest, ' +
|
|
93
|
+
'but hierarchies should be modeled inside \'entries\'.',
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
73
96
|
});
|
|
74
97
|
|
|
75
98
|
it('fails with Error when entries is missing or not an array', () => {
|
|
76
|
-
|
|
77
|
-
tester.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
).toEqual(true);
|
|
99
|
+
tester.validate_manifest(JSON.stringify({specVersion: '1.0'}), 'test');
|
|
100
|
+
expect(has(tester.errors, 'ArdManifest validation failed: \'entries\' is a required property'))
|
|
101
|
+
.toEqual(true);
|
|
102
|
+
expect(has(tester.errors, 'Missing required \'entries\' array')).toEqual(true);
|
|
81
103
|
|
|
82
104
|
tester = new ConformanceTester();
|
|
83
|
-
|
|
84
|
-
tester.
|
|
85
|
-
expect(
|
|
86
|
-
tester.errors.some(i => i.message.includes('\'entries\' must be a JSON array'))
|
|
87
|
-
).toEqual(true);
|
|
105
|
+
tester.validate_manifest(JSON.stringify({entries: 'not-an-array'}), 'test');
|
|
106
|
+
expect(has(tester.errors, '\'entries\' must be a JSON array')).toEqual(true);
|
|
88
107
|
});
|
|
89
108
|
|
|
90
109
|
it('fails with Error when entry is not a JSON object', () => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
110
|
+
tester.validate_manifest(JSON.stringify({entries: ['not an object', null]}), 'test');
|
|
111
|
+
expect(tester.errors).toContainEqual({
|
|
112
|
+
element: 'Entry #0',
|
|
113
|
+
message: expect.stringContaining('ArdEntry validation failed'),
|
|
114
|
+
});
|
|
115
|
+
expect(tester.errors).toContainEqual({
|
|
116
|
+
element: 'Entry #1',
|
|
117
|
+
message: 'Missing required \'identifier\'.',
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('validates each entry against ArdEntry and labels the error', () => {
|
|
122
|
+
const manifest = {entries: [entry({identifier: 'http://not-a-urn'})]};
|
|
95
123
|
tester.validate_manifest(JSON.stringify(manifest), 'test');
|
|
96
|
-
expect(
|
|
97
|
-
|
|
98
|
-
|
|
124
|
+
expect(tester.errors).toContainEqual({
|
|
125
|
+
element: 'Root',
|
|
126
|
+
message: expect.stringMatching(
|
|
127
|
+
/^ArdManifest validation failed: .* at path 'entries\.0\.identifier'$/),
|
|
128
|
+
});
|
|
129
|
+
expect(tester.errors).toContainEqual({
|
|
130
|
+
element: 'Web Search API',
|
|
131
|
+
message: expect.stringMatching(/^ArdEntry validation failed: .* at path 'identifier'$/),
|
|
132
|
+
});
|
|
99
133
|
});
|
|
100
134
|
|
|
101
135
|
it('fails with Error when entry is missing identifier or has invalid RFC 8141 URN', () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
entries: [
|
|
105
|
-
{displayName: 'Test', type: 'application/ai-catalog+json', url: 'https://example.com'},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
tester.validate_manifest(JSON.stringify(manifestMissingId), 'test');
|
|
109
|
-
expect(
|
|
110
|
-
tester.errors.some(i => i.message.includes('Missing required \'identifier\''))
|
|
111
|
-
).toEqual(true);
|
|
136
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({identifier: undefined})]}), 'test');
|
|
137
|
+
expect(has(tester.errors, 'Missing required \'identifier\'')).toEqual(true);
|
|
112
138
|
|
|
113
139
|
tester = new ConformanceTester();
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{
|
|
118
|
-
identifier: 'http://not-a-urn',
|
|
119
|
-
displayName: 'Test',
|
|
120
|
-
type: 'application/ai-catalog+json',
|
|
121
|
-
url: 'https://example.com',
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
};
|
|
125
|
-
tester.validate_manifest(JSON.stringify(manifestInvalidUrn), 'test');
|
|
126
|
-
expect(
|
|
127
|
-
tester.errors.some(
|
|
128
|
-
i => i.message.includes('does not match RFC 8141 URN pattern')
|
|
129
|
-
)
|
|
130
|
-
).toEqual(true);
|
|
140
|
+
tester.validate_manifest(
|
|
141
|
+
JSON.stringify({entries: [entry({identifier: 'http://not-a-urn'})]}), 'test');
|
|
142
|
+
expect(has(tester.errors, 'does not match RFC 8141 URN pattern')).toEqual(true);
|
|
131
143
|
});
|
|
132
144
|
|
|
133
145
|
it('fails with Error when entry is missing displayName or type', () => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
entries: [
|
|
137
|
-
{
|
|
138
|
-
identifier: 'urn:air:google:test:agent',
|
|
139
|
-
type: 'application/ai-catalog+json',
|
|
140
|
-
url: 'https://example.com',
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
};
|
|
144
|
-
tester.validate_manifest(JSON.stringify(manifestMissingName), 'test');
|
|
145
|
-
expect(
|
|
146
|
-
tester.errors.some(
|
|
147
|
-
i => i.message.includes('Missing required \'displayName\'')
|
|
148
|
-
)
|
|
149
|
-
).toEqual(true);
|
|
146
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({displayName: undefined})]}), 'test');
|
|
147
|
+
expect(has(tester.errors, 'Missing required \'displayName\'')).toEqual(true);
|
|
150
148
|
|
|
151
149
|
tester = new ConformanceTester();
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
entries: [
|
|
155
|
-
{
|
|
156
|
-
identifier: 'urn:air:google:test:agent',
|
|
157
|
-
displayName: 'Test Agent',
|
|
158
|
-
url: 'https://example.com',
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
};
|
|
162
|
-
tester.validate_manifest(JSON.stringify(manifestMissingType), 'test');
|
|
163
|
-
expect(
|
|
164
|
-
tester.errors.some(i => i.message.includes('Missing required \'type\''))
|
|
165
|
-
).toEqual(true);
|
|
150
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({type: undefined})]}), 'test');
|
|
151
|
+
expect(has(tester.errors, 'Missing required \'type\'')).toEqual(true);
|
|
166
152
|
});
|
|
167
153
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
154
|
+
describe('media type classification', () => {
|
|
155
|
+
/**
|
|
156
|
+
* @param {unknown} type
|
|
157
|
+
*/
|
|
158
|
+
function classify(type) {
|
|
159
|
+
tester = new ConformanceTester();
|
|
160
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({type})]}), 'test');
|
|
161
|
+
return tester;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
it('accepts standard types, case-insensitively and with parameter whitespace', () => {
|
|
165
|
+
for (const type of [
|
|
166
|
+
'application/ai-catalog+json',
|
|
167
|
+
'APPLICATION/AI-Catalog+JSON',
|
|
168
|
+
'application/agent-skills+json',
|
|
169
|
+
'application/agent-skills+md',
|
|
170
|
+
'application/agent-plugins+zip',
|
|
171
|
+
'application/agent-plugins+gzip',
|
|
172
|
+
'text/markdown; profile="urn:air:agent-skills"',
|
|
173
|
+
'text/markdown;profile="urn:air:agent-skills"',
|
|
174
|
+
]) {
|
|
175
|
+
const result = classify(type);
|
|
176
|
+
expect(result.warnings).toEqual([]);
|
|
177
|
+
expect(result.infos).toEqual([]);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('reports application extension types as info only', () => {
|
|
182
|
+
const result = classify('application/custom-unknown+json');
|
|
183
|
+
expect(result.errors).toEqual([]);
|
|
184
|
+
expect(result.warnings).toEqual([]);
|
|
185
|
+
expect(has(result.infos, 'is a valid application extension media type')).toEqual(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('warns on non-application types that are not standard', () => {
|
|
189
|
+
const result = classify('text/plain');
|
|
190
|
+
expect(has(result.warnings,
|
|
191
|
+
'is not one of standard discovery types: [\'application/ai-catalog+json\'')).toEqual(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('warns on the deprecated MCP server media type (ADR-0008)', () => {
|
|
195
|
+
const result = classify('application/mcp-server+json');
|
|
196
|
+
expect(has(result.warnings,
|
|
197
|
+
'was renamed by ADR-0008. Use \'application/mcp-server-card+json\'.')).toEqual(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('warns when a standard base type has missing or unrecognized parameters', () => {
|
|
201
|
+
expect(has(classify('text/markdown').warnings,
|
|
202
|
+
'missing required parameters: profile="urn:air:agent-skills"')).toEqual(true);
|
|
203
|
+
expect(has(classify('application/ai-catalog+json; v=1').warnings,
|
|
204
|
+
'unrecognized parameters: v')).toEqual(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('warns on invalid or non-string media types', () => {
|
|
208
|
+
expect(has(classify('not a media type').warnings,
|
|
209
|
+
'is not a valid IANA media type')).toEqual(true);
|
|
210
|
+
expect(has(classify('application/ai-catalog+json; a=1; a=2').warnings,
|
|
211
|
+
'is not a valid IANA media type')).toEqual(true);
|
|
212
|
+
expect(has(classify(5).warnings, 'Media type must be a string; got int.')).toEqual(true);
|
|
213
|
+
});
|
|
187
214
|
});
|
|
188
215
|
|
|
189
216
|
it('fails with Error when value-or-reference constraint is violated', () => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
entries: [{
|
|
193
|
-
identifier: 'urn:air:google:test:agent',
|
|
194
|
-
displayName: 'Test',
|
|
195
|
-
type: 'application/ai-catalog+json',
|
|
196
|
-
url: 'https://example.com',
|
|
197
|
-
data: {foo: 'bar'},
|
|
198
|
-
}],
|
|
199
|
-
};
|
|
200
|
-
tester.validate_manifest(JSON.stringify(manifestBoth), 'test');
|
|
201
|
-
expect(
|
|
202
|
-
tester.errors.some(
|
|
203
|
-
i => i.message.includes('both \'url\' and \'data\' are provided')
|
|
204
|
-
)
|
|
205
|
-
).toEqual(true);
|
|
217
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({data: {foo: 'bar'}})]}), 'test');
|
|
218
|
+
expect(has(tester.errors, 'both \'url\' and \'data\' are provided')).toEqual(true);
|
|
206
219
|
|
|
207
220
|
tester = new ConformanceTester();
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
entries: [{
|
|
211
|
-
identifier: 'urn:air:google:test:agent',
|
|
212
|
-
displayName: 'Test',
|
|
213
|
-
type: 'application/ai-catalog+json',
|
|
214
|
-
}],
|
|
215
|
-
};
|
|
216
|
-
tester.validate_manifest(JSON.stringify(manifestNeither), 'test');
|
|
217
|
-
expect(
|
|
218
|
-
tester.errors.some(
|
|
219
|
-
i => i.message.includes('neither \'url\' nor \'data\' is provided')
|
|
220
|
-
)
|
|
221
|
-
).toEqual(true);
|
|
221
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({url: undefined})]}), 'test');
|
|
222
|
+
expect(has(tester.errors, 'neither \'url\' nor \'data\' is provided')).toEqual(true);
|
|
222
223
|
});
|
|
223
224
|
|
|
224
225
|
it('validates representativeQueries type and size constraints', () => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
identifier: 'urn:air:google:test:agent',
|
|
229
|
-
displayName: 'Test',
|
|
230
|
-
type: 'application/ai-catalog+json',
|
|
231
|
-
url: 'https://example.com',
|
|
232
|
-
representativeQueries: 'not an array',
|
|
233
|
-
}],
|
|
234
|
-
};
|
|
235
|
-
tester.validate_manifest(JSON.stringify(manifestNonArray), 'test');
|
|
236
|
-
expect(
|
|
237
|
-
tester.errors.some(i => i.message.includes('must be an array of strings'))
|
|
238
|
-
).toEqual(true);
|
|
226
|
+
tester.validate_manifest(
|
|
227
|
+
JSON.stringify({entries: [entry({representativeQueries: 'not an array'})]}), 'test');
|
|
228
|
+
expect(has(tester.errors, 'must be an array of strings')).toEqual(true);
|
|
239
229
|
|
|
240
230
|
tester = new ConformanceTester();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
identifier: 'urn:air:google:test:agent',
|
|
245
|
-
displayName: 'Test',
|
|
246
|
-
type: 'application/ai-catalog+json',
|
|
247
|
-
url: 'https://example.com',
|
|
248
|
-
representativeQueries: [123, 456],
|
|
249
|
-
}],
|
|
250
|
-
};
|
|
251
|
-
tester.validate_manifest(JSON.stringify(manifestNonString), 'test');
|
|
252
|
-
expect(
|
|
253
|
-
tester.errors.some(i => i.message.includes('is not a string'))
|
|
254
|
-
).toEqual(true);
|
|
231
|
+
tester.validate_manifest(
|
|
232
|
+
JSON.stringify({entries: [entry({representativeQueries: [123, 456]})]}), 'test');
|
|
233
|
+
expect(has(tester.errors, 'is not a string')).toEqual(true);
|
|
255
234
|
|
|
256
235
|
tester = new ConformanceTester();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
identifier: 'urn:air:google:test:agent',
|
|
261
|
-
displayName: 'Test',
|
|
262
|
-
type: 'application/ai-catalog+json',
|
|
263
|
-
url: 'https://example.com',
|
|
264
|
-
representativeQueries: ['only one query'],
|
|
265
|
-
}],
|
|
266
|
-
};
|
|
267
|
-
tester.validate_manifest(JSON.stringify(manifestFewQueries), 'test');
|
|
268
|
-
expect(
|
|
269
|
-
tester.warnings.some(i => i.message.includes('2 to 5 queries are recommended'))
|
|
270
|
-
).toEqual(true);
|
|
236
|
+
tester.validate_manifest(
|
|
237
|
+
JSON.stringify({entries: [entry({representativeQueries: ['only one query']})]}), 'test');
|
|
238
|
+
expect(has(tester.warnings, '2 to 5 queries are recommended')).toEqual(true);
|
|
271
239
|
|
|
272
240
|
tester = new ConformanceTester();
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
displayName: 'Test',
|
|
278
|
-
type: 'application/ai-catalog+json',
|
|
279
|
-
url: 'https://example.com',
|
|
280
|
-
}],
|
|
281
|
-
};
|
|
282
|
-
tester.validate_manifest(JSON.stringify(manifestMissingQueries), 'test');
|
|
283
|
-
expect(
|
|
284
|
-
tester.warnings.some(i => i.message.includes('Missing \'representativeQueries\''))
|
|
285
|
-
).toEqual(true);
|
|
241
|
+
tester.validate_manifest(
|
|
242
|
+
JSON.stringify({entries: [entry({representativeQueries: undefined})]}), 'test');
|
|
243
|
+
expect(tester.errors).toEqual([]);
|
|
244
|
+
expect(has(tester.warnings, 'No \'representativeQueries\'')).toEqual(true);
|
|
286
245
|
});
|
|
287
246
|
|
|
288
247
|
it('validates trustManifest constraints', () => {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
identifier: 'urn:air:google:test:agent',
|
|
293
|
-
displayName: 'Test',
|
|
294
|
-
type: 'application/ai-catalog+json',
|
|
295
|
-
url: 'https://example.com',
|
|
296
|
-
trustManifest: 'not-an-object',
|
|
297
|
-
}],
|
|
298
|
-
};
|
|
299
|
-
tester.validate_manifest(JSON.stringify(manifestInvalidTrust), 'test');
|
|
300
|
-
expect(
|
|
301
|
-
tester.errors.some(
|
|
302
|
-
i => i.message.includes('\'trustManifest\' must be a JSON object')
|
|
303
|
-
)
|
|
304
|
-
).toEqual(true);
|
|
248
|
+
tester.validate_manifest(
|
|
249
|
+
JSON.stringify({entries: [entry({trustManifest: 'not-an-object'})]}), 'test');
|
|
250
|
+
expect(has(tester.errors, '\'trustManifest\' must be a JSON object')).toEqual(true);
|
|
305
251
|
|
|
306
252
|
tester = new ConformanceTester();
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
entries: [{
|
|
310
|
-
identifier: 'urn:air:google:test:agent',
|
|
311
|
-
displayName: 'Test',
|
|
312
|
-
type: 'application/ai-catalog+json',
|
|
313
|
-
url: 'https://example.com',
|
|
314
|
-
trustManifest: {},
|
|
315
|
-
}],
|
|
316
|
-
};
|
|
317
|
-
tester.validate_manifest(JSON.stringify(manifestMissingIdentity), 'test');
|
|
318
|
-
expect(
|
|
319
|
-
tester.errors.some(
|
|
320
|
-
i => i.message.includes('missing required \'identity\' field')
|
|
321
|
-
)
|
|
322
|
-
).toEqual(true);
|
|
253
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({trustManifest: {}})]}), 'test');
|
|
254
|
+
expect(has(tester.errors, 'missing required \'identity\' field')).toEqual(true);
|
|
323
255
|
});
|
|
324
256
|
|
|
325
257
|
it('passes completely when schema is fully conformant with URL or inline data', () => {
|
|
326
258
|
const validManifestUrl = {
|
|
327
|
-
|
|
328
|
-
entries: [
|
|
329
|
-
{
|
|
330
|
-
identifier: 'urn:air:google:search:web-search',
|
|
331
|
-
displayName: 'Web Search API',
|
|
332
|
-
type: 'application/mcp-server-card+json',
|
|
333
|
-
url: 'https://example.com/mcp.json',
|
|
334
|
-
representativeQueries: ['search web', 'find articles'],
|
|
335
|
-
trustManifest: {identity: 'google.com'},
|
|
336
|
-
},
|
|
337
|
-
],
|
|
259
|
+
entries: [entry({trustManifest: {identity: 'google.com'}})],
|
|
338
260
|
};
|
|
339
|
-
tester.validate_manifest(JSON.stringify(validManifestUrl), 'test');
|
|
340
|
-
expect(tester.errors
|
|
261
|
+
expect(tester.validate_manifest(JSON.stringify(validManifestUrl), 'test')).toEqual(true);
|
|
262
|
+
expect(tester.errors).toEqual([]);
|
|
263
|
+
expect(tester.warnings).toEqual([]);
|
|
264
|
+
expect(tester.infos).toEqual([]);
|
|
341
265
|
|
|
342
266
|
tester = new ConformanceTester();
|
|
343
267
|
const validManifestData = {
|
|
344
|
-
specVersion: '1.0',
|
|
345
268
|
entries: [
|
|
346
|
-
{
|
|
269
|
+
entry({
|
|
347
270
|
identifier: 'urn:air:google:tools:math-helper',
|
|
348
271
|
displayName: 'Math Helper',
|
|
349
272
|
type: 'application/agent-card+json',
|
|
273
|
+
url: undefined,
|
|
350
274
|
data: {functions: ['add', 'subtract']},
|
|
351
|
-
|
|
352
|
-
},
|
|
275
|
+
}),
|
|
353
276
|
],
|
|
354
277
|
};
|
|
355
|
-
tester.validate_manifest(JSON.stringify(validManifestData), 'test');
|
|
356
|
-
expect(tester.errors
|
|
278
|
+
expect(tester.validate_manifest(JSON.stringify(validManifestData), 'test')).toEqual(true);
|
|
279
|
+
expect(tester.errors).toEqual([]);
|
|
357
280
|
});
|
|
358
281
|
});
|