lighthouse 13.5.0-dev.20261008 → 13.5.0-dev.20261009
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 +170 -0
- package/cli/test/smokehouse/config/exclusions.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/long-tasks.js +2 -1
- package/core/audits/seo/robots-txt.js +3 -8
- package/core/computed/page-dependency-graph.js +0 -4
- package/core/gather/gatherers/agentic/ard.d.ts +59 -4
- package/core/gather/gatherers/agentic/ard.js +218 -56
- package/package.json +5 -6
- package/shared/localization/locales/en-US.json +13 -4
- package/shared/localization/locales/en-XL.json +13 -4
- package/third-party/ard/README.md +18 -9
- package/third-party/ard/ard-test.js +183 -266
- package/third-party/ard/ard.d.ts +17 -1
- package/third-party/ard/ard.js +276 -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/types/artifacts.d.ts +29 -5
- package/build-tracker.config.js +0 -16
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
|
@@ -7,352 +7,269 @@
|
|
|
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 (ADR-0003)', () => {
|
|
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: expect.stringContaining('Found \'collections\' at root'),
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
73
94
|
});
|
|
74
95
|
|
|
75
96
|
it('fails with Error when entries is missing or not an array', () => {
|
|
76
|
-
|
|
77
|
-
tester.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
).toEqual(true);
|
|
97
|
+
tester.validate_manifest(JSON.stringify({specVersion: '1.0'}), 'test');
|
|
98
|
+
expect(has(tester.errors, 'ArdManifest validation failed: \'entries\' is a required property'))
|
|
99
|
+
.toEqual(true);
|
|
100
|
+
expect(has(tester.errors, 'Missing required \'entries\' array')).toEqual(true);
|
|
81
101
|
|
|
82
102
|
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);
|
|
103
|
+
tester.validate_manifest(JSON.stringify({entries: 'not-an-array'}), 'test');
|
|
104
|
+
expect(has(tester.errors, '\'entries\' must be a JSON array')).toEqual(true);
|
|
88
105
|
});
|
|
89
106
|
|
|
90
107
|
it('fails with Error when entry is not a JSON object', () => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
tester.validate_manifest(JSON.stringify({entries: ['not an object', null]}), 'test');
|
|
109
|
+
expect(tester.errors).toContainEqual({
|
|
110
|
+
element: 'Entry #0',
|
|
111
|
+
message: expect.stringContaining('ArdEntry validation failed'),
|
|
112
|
+
});
|
|
113
|
+
expect(tester.errors).toContainEqual({
|
|
114
|
+
element: 'Entry #1',
|
|
115
|
+
message: 'Missing required \'identifier\'.',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('validates each entry against ArdEntry and labels the error', () => {
|
|
120
|
+
const manifest = {entries: [entry({identifier: 'http://not-a-urn'})]};
|
|
95
121
|
tester.validate_manifest(JSON.stringify(manifest), 'test');
|
|
96
|
-
expect(
|
|
97
|
-
|
|
98
|
-
|
|
122
|
+
expect(tester.errors).toContainEqual({
|
|
123
|
+
element: 'Root',
|
|
124
|
+
message: expect.stringMatching(
|
|
125
|
+
/^ArdManifest validation failed: .* at path 'entries\.0\.identifier'$/),
|
|
126
|
+
});
|
|
127
|
+
expect(tester.errors).toContainEqual({
|
|
128
|
+
element: 'Web Search API',
|
|
129
|
+
message: expect.stringMatching(/^ArdEntry validation failed: .* at path 'identifier'$/),
|
|
130
|
+
});
|
|
99
131
|
});
|
|
100
132
|
|
|
101
133
|
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);
|
|
134
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({identifier: undefined})]}), 'test');
|
|
135
|
+
expect(has(tester.errors, 'Missing required \'identifier\'')).toEqual(true);
|
|
112
136
|
|
|
113
137
|
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);
|
|
138
|
+
tester.validate_manifest(
|
|
139
|
+
JSON.stringify({entries: [entry({identifier: 'http://not-a-urn'})]}), 'test');
|
|
140
|
+
expect(has(tester.errors, 'does not match RFC 8141 URN pattern')).toEqual(true);
|
|
131
141
|
});
|
|
132
142
|
|
|
133
143
|
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);
|
|
144
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({displayName: undefined})]}), 'test');
|
|
145
|
+
expect(has(tester.errors, 'Missing required \'displayName\'')).toEqual(true);
|
|
150
146
|
|
|
151
147
|
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);
|
|
148
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({type: undefined})]}), 'test');
|
|
149
|
+
expect(has(tester.errors, 'Missing required \'type\'')).toEqual(true);
|
|
166
150
|
});
|
|
167
151
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
152
|
+
describe('media type classification', () => {
|
|
153
|
+
/**
|
|
154
|
+
* @param {unknown} type
|
|
155
|
+
*/
|
|
156
|
+
function classify(type) {
|
|
157
|
+
tester = new ConformanceTester();
|
|
158
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({type})]}), 'test');
|
|
159
|
+
return tester;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
it('accepts standard types, case-insensitively and with parameter whitespace', () => {
|
|
163
|
+
for (const type of [
|
|
164
|
+
'application/ai-catalog+json',
|
|
165
|
+
'APPLICATION/AI-Catalog+JSON',
|
|
166
|
+
'text/markdown; profile="urn:air:agent-skills"',
|
|
167
|
+
'text/markdown;profile="urn:air:agent-skills"',
|
|
168
|
+
]) {
|
|
169
|
+
const result = classify(type);
|
|
170
|
+
expect(result.warnings).toEqual([]);
|
|
171
|
+
expect(result.infos).toEqual([]);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('reports application extension types as info only', () => {
|
|
176
|
+
const result = classify('application/custom-unknown+json');
|
|
177
|
+
expect(result.errors).toEqual([]);
|
|
178
|
+
expect(result.warnings).toEqual([]);
|
|
179
|
+
expect(has(result.infos, 'is a valid application extension media type')).toEqual(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('warns on non-application types that are not standard', () => {
|
|
183
|
+
const result = classify('text/plain');
|
|
184
|
+
expect(has(result.warnings,
|
|
185
|
+
'is not one of standard discovery types: [\'application/ai-catalog+json\'')).toEqual(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('warns on the deprecated MCP server media type (ADR-0008)', () => {
|
|
189
|
+
const result = classify('application/mcp-server+json');
|
|
190
|
+
expect(has(result.warnings,
|
|
191
|
+
'was renamed by ADR-0008. Use \'application/mcp-server-card+json\'.')).toEqual(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('warns when a standard base type has missing or unrecognized parameters', () => {
|
|
195
|
+
expect(has(classify('text/markdown').warnings,
|
|
196
|
+
'missing required parameters: profile="urn:air:agent-skills"')).toEqual(true);
|
|
197
|
+
expect(has(classify('application/ai-catalog+json; v=1').warnings,
|
|
198
|
+
'unrecognized parameters: v')).toEqual(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('warns on invalid or non-string media types', () => {
|
|
202
|
+
expect(has(classify('not a media type').warnings,
|
|
203
|
+
'is not a valid IANA media type')).toEqual(true);
|
|
204
|
+
expect(has(classify('application/ai-catalog+json; a=1; a=2').warnings,
|
|
205
|
+
'is not a valid IANA media type')).toEqual(true);
|
|
206
|
+
expect(has(classify(5).warnings, 'Media type must be a string; got int.')).toEqual(true);
|
|
207
|
+
});
|
|
187
208
|
});
|
|
188
209
|
|
|
189
210
|
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);
|
|
211
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({data: {foo: 'bar'}})]}), 'test');
|
|
212
|
+
expect(has(tester.errors, 'both \'url\' and \'data\' are provided')).toEqual(true);
|
|
206
213
|
|
|
207
214
|
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);
|
|
215
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({url: undefined})]}), 'test');
|
|
216
|
+
expect(has(tester.errors, 'neither \'url\' nor \'data\' is provided')).toEqual(true);
|
|
222
217
|
});
|
|
223
218
|
|
|
224
219
|
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);
|
|
220
|
+
tester.validate_manifest(
|
|
221
|
+
JSON.stringify({entries: [entry({representativeQueries: 'not an array'})]}), 'test');
|
|
222
|
+
expect(has(tester.errors, 'must be an array of strings')).toEqual(true);
|
|
239
223
|
|
|
240
224
|
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);
|
|
225
|
+
tester.validate_manifest(
|
|
226
|
+
JSON.stringify({entries: [entry({representativeQueries: [123, 456]})]}), 'test');
|
|
227
|
+
expect(has(tester.errors, 'is not a string')).toEqual(true);
|
|
255
228
|
|
|
256
229
|
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);
|
|
230
|
+
tester.validate_manifest(
|
|
231
|
+
JSON.stringify({entries: [entry({representativeQueries: ['only one query']})]}), 'test');
|
|
232
|
+
expect(has(tester.warnings, '2 to 5 queries are recommended')).toEqual(true);
|
|
271
233
|
|
|
272
234
|
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);
|
|
235
|
+
tester.validate_manifest(
|
|
236
|
+
JSON.stringify({entries: [entry({representativeQueries: undefined})]}), 'test');
|
|
237
|
+
expect(tester.errors).toEqual([]);
|
|
238
|
+
expect(has(tester.warnings, 'No \'representativeQueries\'')).toEqual(true);
|
|
286
239
|
});
|
|
287
240
|
|
|
288
241
|
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);
|
|
242
|
+
tester.validate_manifest(
|
|
243
|
+
JSON.stringify({entries: [entry({trustManifest: 'not-an-object'})]}), 'test');
|
|
244
|
+
expect(has(tester.errors, '\'trustManifest\' must be a JSON object')).toEqual(true);
|
|
305
245
|
|
|
306
246
|
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);
|
|
247
|
+
tester.validate_manifest(JSON.stringify({entries: [entry({trustManifest: {}})]}), 'test');
|
|
248
|
+
expect(has(tester.errors, 'missing required \'identity\' field')).toEqual(true);
|
|
323
249
|
});
|
|
324
250
|
|
|
325
251
|
it('passes completely when schema is fully conformant with URL or inline data', () => {
|
|
326
252
|
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
|
-
],
|
|
253
|
+
entries: [entry({trustManifest: {identity: 'google.com'}})],
|
|
338
254
|
};
|
|
339
|
-
tester.validate_manifest(JSON.stringify(validManifestUrl), 'test');
|
|
340
|
-
expect(tester.errors
|
|
255
|
+
expect(tester.validate_manifest(JSON.stringify(validManifestUrl), 'test')).toEqual(true);
|
|
256
|
+
expect(tester.errors).toEqual([]);
|
|
257
|
+
expect(tester.warnings).toEqual([]);
|
|
258
|
+
expect(tester.infos).toEqual([]);
|
|
341
259
|
|
|
342
260
|
tester = new ConformanceTester();
|
|
343
261
|
const validManifestData = {
|
|
344
|
-
specVersion: '1.0',
|
|
345
262
|
entries: [
|
|
346
|
-
{
|
|
263
|
+
entry({
|
|
347
264
|
identifier: 'urn:air:google:tools:math-helper',
|
|
348
265
|
displayName: 'Math Helper',
|
|
349
266
|
type: 'application/agent-card+json',
|
|
267
|
+
url: undefined,
|
|
350
268
|
data: {functions: ['add', 'subtract']},
|
|
351
|
-
|
|
352
|
-
},
|
|
269
|
+
}),
|
|
353
270
|
],
|
|
354
271
|
};
|
|
355
|
-
tester.validate_manifest(JSON.stringify(validManifestData), 'test');
|
|
356
|
-
expect(tester.errors
|
|
272
|
+
expect(tester.validate_manifest(JSON.stringify(validManifestData), 'test')).toEqual(true);
|
|
273
|
+
expect(tester.errors).toEqual([]);
|
|
357
274
|
});
|
|
358
275
|
});
|
package/third-party/ard/ard.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type ParsedMediaType = [string, Array<[string, string]>];
|
|
1
2
|
export type ValidationError = {
|
|
2
3
|
element: string;
|
|
3
4
|
message: string;
|
|
@@ -13,6 +14,8 @@ export class ConformanceTester {
|
|
|
13
14
|
errors: ValidationError[];
|
|
14
15
|
/** @type {ValidationError[]} */
|
|
15
16
|
warnings: ValidationError[];
|
|
17
|
+
/** @type {ValidationError[]} */
|
|
18
|
+
infos: ValidationError[];
|
|
16
19
|
/**
|
|
17
20
|
* @param {string} message
|
|
18
21
|
* @param {string} [element='Root']
|
|
@@ -23,7 +26,15 @@ export class ConformanceTester {
|
|
|
23
26
|
* @param {string} [element='Root']
|
|
24
27
|
*/
|
|
25
28
|
add_warning(message: string, element?: string): void;
|
|
26
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} message
|
|
31
|
+
* @param {string} [element='Root']
|
|
32
|
+
*/
|
|
33
|
+
add_info(message: string, element?: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Validate the manifest as an ArdManifest, then every entry as an ArdEntry.
|
|
36
|
+
* @param {any} manifest_data
|
|
37
|
+
*/
|
|
27
38
|
run_json_schema_validation(manifest_data: any): boolean;
|
|
28
39
|
/**
|
|
29
40
|
* @param {string} raw_content
|
|
@@ -31,4 +42,9 @@ export class ConformanceTester {
|
|
|
31
42
|
*/
|
|
32
43
|
validate_manifest(raw_content: string, source_label: string): boolean;
|
|
33
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* @param {unknown} media_type
|
|
47
|
+
* @return {['warning'|'info', string]|null}
|
|
48
|
+
*/
|
|
49
|
+
export function classify_media_type(media_type: unknown): ["warning" | "info", string] | null;
|
|
34
50
|
//# sourceMappingURL=ard.d.ts.map
|