@probemesh/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +160 -0
- package/dist/chunk-257Y7LN2.js +827 -0
- package/dist/chunk-257Y7LN2.js.map +1 -0
- package/dist/chunk-2ASMVLG4.js +56 -0
- package/dist/chunk-2ASMVLG4.js.map +1 -0
- package/dist/chunk-2KWGHJYP.js +285 -0
- package/dist/chunk-2KWGHJYP.js.map +1 -0
- package/dist/chunk-3H7UGVI6.js +1117 -0
- package/dist/chunk-3H7UGVI6.js.map +1 -0
- package/dist/chunk-5Q3PDYIA.js +657 -0
- package/dist/chunk-5Q3PDYIA.js.map +1 -0
- package/dist/chunk-CXZOO3U4.js +550 -0
- package/dist/chunk-CXZOO3U4.js.map +1 -0
- package/dist/chunk-FRFBK4SY.js +270 -0
- package/dist/chunk-FRFBK4SY.js.map +1 -0
- package/dist/chunk-HJK52QJF.js +994 -0
- package/dist/chunk-HJK52QJF.js.map +1 -0
- package/dist/chunk-HNBDX7IU.js +705 -0
- package/dist/chunk-HNBDX7IU.js.map +1 -0
- package/dist/chunk-NYTV263W.js +116 -0
- package/dist/chunk-NYTV263W.js.map +1 -0
- package/dist/chunk-PXG54XOG.js +595 -0
- package/dist/chunk-PXG54XOG.js.map +1 -0
- package/dist/chunk-TDOBAMYM.js +607 -0
- package/dist/chunk-TDOBAMYM.js.map +1 -0
- package/dist/chunk-TV42EZSI.js +2157 -0
- package/dist/chunk-TV42EZSI.js.map +1 -0
- package/dist/chunk-UU2ZG7P7.js +408 -0
- package/dist/chunk-UU2ZG7P7.js.map +1 -0
- package/dist/chunk-WKN7QOCA.js +977 -0
- package/dist/chunk-WKN7QOCA.js.map +1 -0
- package/dist/chunk-ZJOLPBJJ.js +1091 -0
- package/dist/chunk-ZJOLPBJJ.js.map +1 -0
- package/dist/cli/audit-trail-export.cjs +1193 -0
- package/dist/cli/audit-trail-export.cjs.map +1 -0
- package/dist/cli/audit-trail-export.d.cts +1 -0
- package/dist/cli/audit-trail-export.d.ts +1 -0
- package/dist/cli/audit-trail-export.js +24 -0
- package/dist/cli/audit-trail-export.js.map +1 -0
- package/dist/cli/catalog-check.cjs +2687 -0
- package/dist/cli/catalog-check.cjs.map +1 -0
- package/dist/cli/catalog-check.d.cts +1 -0
- package/dist/cli/catalog-check.d.ts +1 -0
- package/dist/cli/catalog-check.js +26 -0
- package/dist/cli/catalog-check.js.map +1 -0
- package/dist/cli/probemesh-init.cjs +1049 -0
- package/dist/cli/probemesh-init.cjs.map +1 -0
- package/dist/cli/probemesh-init.d.cts +1 -0
- package/dist/cli/probemesh-init.d.ts +1 -0
- package/dist/cli/probemesh-init.js +22 -0
- package/dist/cli/probemesh-init.js.map +1 -0
- package/dist/cli/provider-conformance.cjs +6180 -0
- package/dist/cli/provider-conformance.cjs.map +1 -0
- package/dist/cli/provider-conformance.d.cts +1 -0
- package/dist/cli/provider-conformance.d.ts +1 -0
- package/dist/cli/provider-conformance.js +29 -0
- package/dist/cli/provider-conformance.js.map +1 -0
- package/dist/cli/provider-dossier-check.cjs +2978 -0
- package/dist/cli/provider-dossier-check.cjs.map +1 -0
- package/dist/cli/provider-dossier-check.d.cts +1 -0
- package/dist/cli/provider-dossier-check.d.ts +1 -0
- package/dist/cli/provider-dossier-check.js +27 -0
- package/dist/cli/provider-dossier-check.js.map +1 -0
- package/dist/cli/provider-dossier.cjs +1753 -0
- package/dist/cli/provider-dossier.cjs.map +1 -0
- package/dist/cli/provider-dossier.d.cts +1 -0
- package/dist/cli/provider-dossier.d.ts +1 -0
- package/dist/cli/provider-dossier.js +26 -0
- package/dist/cli/provider-dossier.js.map +1 -0
- package/dist/cli/x402-accept.cjs +6009 -0
- package/dist/cli/x402-accept.cjs.map +1 -0
- package/dist/cli/x402-accept.d.cts +1 -0
- package/dist/cli/x402-accept.d.ts +1 -0
- package/dist/cli/x402-accept.js +28 -0
- package/dist/cli/x402-accept.js.map +1 -0
- package/dist/index.cjs +13671 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2026 -0
- package/dist/index.d.ts +2026 -0
- package/dist/index.js +2560 -0
- package/dist/index.js.map +1 -0
- package/package.json +111 -0
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createProviderConformanceArtifact,
|
|
3
|
+
formatProviderConformanceArtifact
|
|
4
|
+
} from "./chunk-UU2ZG7P7.js";
|
|
5
|
+
import {
|
|
6
|
+
createProbeMesh,
|
|
7
|
+
startHostedRouter
|
|
8
|
+
} from "./chunk-TV42EZSI.js";
|
|
9
|
+
import {
|
|
10
|
+
isSharedJsonSchemaLike,
|
|
11
|
+
validateProviderManifest
|
|
12
|
+
} from "./chunk-3H7UGVI6.js";
|
|
13
|
+
import {
|
|
14
|
+
loadCliConfigDefault,
|
|
15
|
+
redactX402Secrets
|
|
16
|
+
} from "./chunk-NYTV263W.js";
|
|
17
|
+
import {
|
|
18
|
+
ProbeMeshError,
|
|
19
|
+
isProbeMeshError
|
|
20
|
+
} from "./chunk-2ASMVLG4.js";
|
|
21
|
+
|
|
22
|
+
// src/schemaValidation.ts
|
|
23
|
+
function validateJsonSchemaLikeValue(value, schema, options = {}) {
|
|
24
|
+
const errors = [];
|
|
25
|
+
validateValue(value, schema, options.path ?? "$", errors);
|
|
26
|
+
return {
|
|
27
|
+
valid: errors.length === 0,
|
|
28
|
+
errors
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function assertJsonSchemaLikeValue(value, schema, options = {}) {
|
|
32
|
+
const result = validateJsonSchemaLikeValue(value, schema, options);
|
|
33
|
+
if (result.valid) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
throw new ProbeMeshError({
|
|
37
|
+
code: "invalid_request",
|
|
38
|
+
message: `JSON schema-lite validation failed: ${formatValidationErrors(
|
|
39
|
+
result.errors
|
|
40
|
+
)}`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function validateValue(value, schema, path, errors) {
|
|
44
|
+
if (!isRecord(schema)) {
|
|
45
|
+
errors.push({
|
|
46
|
+
path,
|
|
47
|
+
message: "schema must be an object."
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (schema.enum !== void 0) {
|
|
52
|
+
if (!Array.isArray(schema.enum)) {
|
|
53
|
+
errors.push({
|
|
54
|
+
path,
|
|
55
|
+
message: "schema enum must be an array."
|
|
56
|
+
});
|
|
57
|
+
} else if (!schema.enum.some((candidate) => deepEqual(candidate, value))) {
|
|
58
|
+
errors.push({
|
|
59
|
+
path,
|
|
60
|
+
message: "value must match one of the schema enum values."
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (schema.type !== void 0) {
|
|
65
|
+
validateType(value, schema.type, path, errors);
|
|
66
|
+
}
|
|
67
|
+
if (isRecord(value)) {
|
|
68
|
+
validateObjectValue(value, schema, path, errors);
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
validateArrayValue(value, schema, path, errors);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function validateType(value, type, path, errors) {
|
|
75
|
+
if (typeof type !== "string") {
|
|
76
|
+
errors.push({
|
|
77
|
+
path,
|
|
78
|
+
message: "schema type must be a string."
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const valid = type === "object" ? isRecord(value) : type === "array" ? Array.isArray(value) : type === "string" ? typeof value === "string" : type === "number" ? typeof value === "number" && Number.isFinite(value) : type === "integer" ? Number.isInteger(value) : type === "boolean" ? typeof value === "boolean" : type === "null" ? value === null : true;
|
|
83
|
+
if (!valid) {
|
|
84
|
+
errors.push({
|
|
85
|
+
path,
|
|
86
|
+
message: `value must be ${type}.`
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function validateObjectValue(value, schema, path, errors) {
|
|
91
|
+
if (schema.required !== void 0) {
|
|
92
|
+
if (!Array.isArray(schema.required)) {
|
|
93
|
+
errors.push({
|
|
94
|
+
path,
|
|
95
|
+
message: "schema required must be an array."
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
for (const requiredKey of schema.required) {
|
|
99
|
+
if (typeof requiredKey !== "string") {
|
|
100
|
+
errors.push({
|
|
101
|
+
path,
|
|
102
|
+
message: "schema required entries must be strings."
|
|
103
|
+
});
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (!(requiredKey in value)) {
|
|
107
|
+
errors.push({
|
|
108
|
+
path: `${path}.${requiredKey}`,
|
|
109
|
+
message: "required property is missing."
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const properties = isRecord(schema.properties) ? schema.properties : void 0;
|
|
116
|
+
if (properties) {
|
|
117
|
+
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
118
|
+
if (key in value) {
|
|
119
|
+
validateValue(
|
|
120
|
+
value[key],
|
|
121
|
+
propertySchema,
|
|
122
|
+
`${path}.${key}`,
|
|
123
|
+
errors
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (schema.additionalProperties === false && properties) {
|
|
129
|
+
for (const key of Object.keys(value)) {
|
|
130
|
+
if (!(key in properties)) {
|
|
131
|
+
errors.push({
|
|
132
|
+
path: `${path}.${key}`,
|
|
133
|
+
message: "additional property is not allowed."
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else if (isRecord(schema.additionalProperties)) {
|
|
138
|
+
const additionalSchema = schema.additionalProperties;
|
|
139
|
+
for (const key of Object.keys(value)) {
|
|
140
|
+
if (!properties || !(key in properties)) {
|
|
141
|
+
validateValue(value[key], additionalSchema, `${path}.${key}`, errors);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function validateArrayValue(value, schema, path, errors) {
|
|
147
|
+
if (schema.items === void 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!isRecord(schema.items)) {
|
|
151
|
+
errors.push({
|
|
152
|
+
path,
|
|
153
|
+
message: "schema items must be an object."
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
value.forEach((item, index) => {
|
|
158
|
+
validateValue(item, schema.items, `${path}.${index}`, errors);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function isRecord(value) {
|
|
162
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
163
|
+
}
|
|
164
|
+
function deepEqual(left, right) {
|
|
165
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
166
|
+
}
|
|
167
|
+
function formatValidationErrors(errors) {
|
|
168
|
+
return errors.map((error) => `${error.path}: ${error.message}`).join("; ");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/providerConformance.ts
|
|
172
|
+
var DEFAULT_REQUIRED_RECEIPT_TYPES = ["provider_delivery"];
|
|
173
|
+
async function runProviderConformance(options) {
|
|
174
|
+
assertConformanceOptions(options);
|
|
175
|
+
const requiredReceiptTypes = options.requiredReceiptTypes ?? DEFAULT_REQUIRED_RECEIPT_TYPES;
|
|
176
|
+
const schemaValidation = options.schemaValidation ?? {};
|
|
177
|
+
const forbiddenOutputValues = options.forbiddenOutputValues ?? [];
|
|
178
|
+
const manifestValidation = validateProviderManifest(options.manifest);
|
|
179
|
+
const setupChecks = [
|
|
180
|
+
manifestValidation.valid ? pass("manifest_valid", "Provider manifest is valid.") : fail("manifest_valid", "Provider manifest is invalid.", {
|
|
181
|
+
errors: manifestValidation.errors
|
|
182
|
+
})
|
|
183
|
+
];
|
|
184
|
+
const inputSchema = schemaValidation.input ? schemaForCapability(options.manifest.inputSchema, options.request.capability) : void 0;
|
|
185
|
+
if (inputSchema) {
|
|
186
|
+
const inputValidation = validateJsonSchemaLikeValue(
|
|
187
|
+
options.request.input,
|
|
188
|
+
inputSchema,
|
|
189
|
+
{
|
|
190
|
+
path: "$.input"
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
setupChecks.push(
|
|
194
|
+
inputValidation.valid ? pass("input_schema_valid", "Request input matches the declared schema.") : fail("input_schema_valid", "Request input does not match the declared schema.", {
|
|
195
|
+
errors: inputValidation.errors
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
} else if (schemaValidation.input) {
|
|
199
|
+
setupChecks.push(
|
|
200
|
+
fail("input_schema_valid", "No input schema is declared for this capability.")
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (setupChecks.some((check) => check.status === "failed")) {
|
|
204
|
+
const checks2 = [
|
|
205
|
+
...setupChecks,
|
|
206
|
+
createSecretSafeOutputCheck({
|
|
207
|
+
checks: setupChecks,
|
|
208
|
+
requiredReceiptTypes,
|
|
209
|
+
telemetryEvents: [],
|
|
210
|
+
forbiddenOutputValues
|
|
211
|
+
})
|
|
212
|
+
];
|
|
213
|
+
const status2 = statusFromChecks(checks2);
|
|
214
|
+
const sanitizedChecks2 = sanitizeConformanceOutput(checks2, forbiddenOutputValues);
|
|
215
|
+
return {
|
|
216
|
+
status: status2,
|
|
217
|
+
checks: sanitizedChecks2,
|
|
218
|
+
telemetryEvents: [],
|
|
219
|
+
summary: createSummary({
|
|
220
|
+
status: status2,
|
|
221
|
+
checks: sanitizedChecks2,
|
|
222
|
+
telemetryEvents: [],
|
|
223
|
+
requiredReceiptTypes
|
|
224
|
+
})
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const telemetryEvents = [];
|
|
228
|
+
const hostedRouter = await startHostedRouter({
|
|
229
|
+
port: 0,
|
|
230
|
+
adapters: options.adapters,
|
|
231
|
+
apiKeys: options.apiKey ? [options.apiKey] : void 0,
|
|
232
|
+
onTelemetryEvent(event) {
|
|
233
|
+
telemetryEvents.push(event);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
let response;
|
|
237
|
+
let error;
|
|
238
|
+
try {
|
|
239
|
+
const probemesh = createProbeMesh({
|
|
240
|
+
hosted: {
|
|
241
|
+
baseUrl: hostedRouter.url,
|
|
242
|
+
apiKey: options.apiKey
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
response = await probemesh.call(options.request);
|
|
247
|
+
} catch (caught) {
|
|
248
|
+
error = toConformanceError(caught);
|
|
249
|
+
}
|
|
250
|
+
} finally {
|
|
251
|
+
await hostedRouter.close();
|
|
252
|
+
}
|
|
253
|
+
const baseChecks = [
|
|
254
|
+
...setupChecks,
|
|
255
|
+
...createExecutionChecks({
|
|
256
|
+
response,
|
|
257
|
+
error,
|
|
258
|
+
telemetryEvents,
|
|
259
|
+
expectedProviderId: options.expectedProviderId ?? options.manifest.id,
|
|
260
|
+
requiredReceiptTypes
|
|
261
|
+
})
|
|
262
|
+
];
|
|
263
|
+
const outputSchema = schemaValidation.output ? schemaForCapability(options.manifest.outputSchema, options.request.capability) : void 0;
|
|
264
|
+
if (outputSchema) {
|
|
265
|
+
baseChecks.push(outputSchemaCheck(response, outputSchema));
|
|
266
|
+
} else if (schemaValidation.output) {
|
|
267
|
+
baseChecks.push(
|
|
268
|
+
fail("output_schema_valid", "No output schema is declared for this capability.")
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
const checks = [
|
|
272
|
+
...baseChecks,
|
|
273
|
+
createSecretSafeOutputCheck({
|
|
274
|
+
response,
|
|
275
|
+
error,
|
|
276
|
+
telemetryEvents,
|
|
277
|
+
checks: baseChecks,
|
|
278
|
+
requiredReceiptTypes,
|
|
279
|
+
forbiddenOutputValues
|
|
280
|
+
})
|
|
281
|
+
];
|
|
282
|
+
const status = statusFromChecks(checks);
|
|
283
|
+
const sanitizedChecks = sanitizeConformanceOutput(checks, forbiddenOutputValues);
|
|
284
|
+
const sanitizedResponse = sanitizeConformanceOutput(response, forbiddenOutputValues);
|
|
285
|
+
const sanitizedError = sanitizeConformanceOutput(error, forbiddenOutputValues);
|
|
286
|
+
const sanitizedTelemetryEvents = sanitizeConformanceOutput(
|
|
287
|
+
telemetryEvents,
|
|
288
|
+
forbiddenOutputValues
|
|
289
|
+
);
|
|
290
|
+
return {
|
|
291
|
+
status,
|
|
292
|
+
checks: sanitizedChecks,
|
|
293
|
+
response: sanitizedResponse,
|
|
294
|
+
error: sanitizedError,
|
|
295
|
+
telemetryEvents: sanitizedTelemetryEvents,
|
|
296
|
+
summary: createSummary({
|
|
297
|
+
status,
|
|
298
|
+
checks: sanitizedChecks,
|
|
299
|
+
response: sanitizedResponse,
|
|
300
|
+
error: sanitizedError,
|
|
301
|
+
telemetryEvents: sanitizedTelemetryEvents,
|
|
302
|
+
requiredReceiptTypes
|
|
303
|
+
})
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function createProviderConformanceReport(result, options = {}) {
|
|
307
|
+
const receiptRefs = result.response?.receiptRefs ?? result.error?.receiptRefs;
|
|
308
|
+
const report = {
|
|
309
|
+
title: options.title ?? "ProbeMesh Provider Conformance Report",
|
|
310
|
+
status: result.status,
|
|
311
|
+
summary: result.summary,
|
|
312
|
+
checks: result.checks,
|
|
313
|
+
receipts: summarizeReceipts(receiptRefs),
|
|
314
|
+
route: result.summary.route,
|
|
315
|
+
timelineEventTypes: result.summary.timelineEventTypes,
|
|
316
|
+
telemetryEventTypes: result.summary.telemetryEventTypes,
|
|
317
|
+
safety: result.summary.safety,
|
|
318
|
+
retrySafety: result.summary.retrySafety,
|
|
319
|
+
error: result.error
|
|
320
|
+
};
|
|
321
|
+
return sanitizeConformanceOutput(report, options.forbiddenOutputValues ?? []);
|
|
322
|
+
}
|
|
323
|
+
function formatProviderConformanceReport(report, options = {}) {
|
|
324
|
+
const sanitizedReport = sanitizeConformanceOutput(report, []);
|
|
325
|
+
if ((options.format ?? "markdown") === "json") {
|
|
326
|
+
return JSON.stringify(sanitizedReport, null, 2);
|
|
327
|
+
}
|
|
328
|
+
return formatConformanceReportMarkdown(sanitizedReport);
|
|
329
|
+
}
|
|
330
|
+
function createExecutionChecks(options) {
|
|
331
|
+
return [
|
|
332
|
+
completedResponseCheck(options.response, options.error),
|
|
333
|
+
selectedProviderCheck(
|
|
334
|
+
options.response,
|
|
335
|
+
options.error,
|
|
336
|
+
options.expectedProviderId
|
|
337
|
+
),
|
|
338
|
+
routeMetadataCheck(options.response, options.error),
|
|
339
|
+
timelineEventsCheck(options.response, options.error),
|
|
340
|
+
requiredReceiptsCheck(options.response, options.error, options.requiredReceiptTypes),
|
|
341
|
+
safetyMetadataCheck(options.response, options.error, options.requiredReceiptTypes),
|
|
342
|
+
retrySafetyCheck(options.response, options.error),
|
|
343
|
+
telemetryMatchesTimelineCheck(options.response, options.telemetryEvents)
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
function completedResponseCheck(response, error) {
|
|
347
|
+
if (response?.status === "completed") {
|
|
348
|
+
return pass("completed_response", "Hosted provider call completed.");
|
|
349
|
+
}
|
|
350
|
+
return fail(
|
|
351
|
+
"completed_response",
|
|
352
|
+
error ? `Hosted provider call failed with ${error.code}.` : "Hosted provider call did not return a completed response.",
|
|
353
|
+
{
|
|
354
|
+
errorCode: error?.code,
|
|
355
|
+
errorMessage: error?.message
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
function selectedProviderCheck(response, error, expectedProviderId) {
|
|
360
|
+
const providerId = response?.provider.id;
|
|
361
|
+
if (providerId && (!expectedProviderId || providerId === expectedProviderId)) {
|
|
362
|
+
return pass("selected_provider", "Hosted route selected the expected provider.", {
|
|
363
|
+
providerId
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return fail("selected_provider", "Hosted route did not select the expected provider.", {
|
|
367
|
+
expectedProviderId,
|
|
368
|
+
providerId,
|
|
369
|
+
errorProvider: error?.provider
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function routeMetadataCheck(response, error) {
|
|
373
|
+
if (response?.route?.mode === "hosted" && typeof response.route.routeId === "string" && response.route.routeId.length > 0) {
|
|
374
|
+
return pass("route_metadata", "Hosted route metadata is present.", {
|
|
375
|
+
routeId: response.route.routeId,
|
|
376
|
+
selectedProviderId: response.route.selectedProviderId
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return fail("route_metadata", "Hosted route metadata is missing.", {
|
|
380
|
+
errorRouteId: error?.route?.routeId
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function timelineEventsCheck(response, error) {
|
|
384
|
+
const timelineEventTypes = response?.timeline?.events.map((event) => event.type) ?? [];
|
|
385
|
+
const requiredEvents = [
|
|
386
|
+
"call.requested",
|
|
387
|
+
"provider.attempted",
|
|
388
|
+
"provider.selected",
|
|
389
|
+
"call.completed"
|
|
390
|
+
];
|
|
391
|
+
const missingEvents = requiredEvents.filter(
|
|
392
|
+
(eventType) => !timelineEventTypes.includes(eventType)
|
|
393
|
+
);
|
|
394
|
+
if (missingEvents.length === 0) {
|
|
395
|
+
return pass("timeline_events", "Hosted timeline contains the provider conformance events.", {
|
|
396
|
+
timelineEventTypes
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return fail("timeline_events", "Hosted timeline is missing required events.", {
|
|
400
|
+
missingEvents,
|
|
401
|
+
timelineEventTypes,
|
|
402
|
+
errorTimelineEventTypes: error?.timeline?.events.map((event) => event.type)
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function requiredReceiptsCheck(response, error, requiredReceiptTypes) {
|
|
406
|
+
const receiptTypes = getReceiptTypes(response?.receiptRefs);
|
|
407
|
+
const missingReceiptTypes = requiredReceiptTypes.filter(
|
|
408
|
+
(receiptType) => !receiptTypes.includes(receiptType)
|
|
409
|
+
);
|
|
410
|
+
if (missingReceiptTypes.length === 0) {
|
|
411
|
+
return pass("required_receipts", "Required receipts are present.", {
|
|
412
|
+
receiptTypes
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return fail("required_receipts", "Required receipts are missing.", {
|
|
416
|
+
missingReceiptTypes,
|
|
417
|
+
receiptTypes,
|
|
418
|
+
errorReceiptTypes: getReceiptTypes(error?.receiptRefs)
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function safetyMetadataCheck(response, error, requiredReceiptTypes) {
|
|
422
|
+
const requiresPaymentSafety = requiredReceiptTypes.some(
|
|
423
|
+
(receiptType) => ["payment_proof", "settlement_confirmation"].includes(receiptType)
|
|
424
|
+
);
|
|
425
|
+
if (!requiresPaymentSafety) {
|
|
426
|
+
return pass("safety_metadata", "Payment safety metadata is not required for this conformance run.");
|
|
427
|
+
}
|
|
428
|
+
if (response?.safety) {
|
|
429
|
+
return pass("safety_metadata", "Safety metadata is present for paid conformance.", {
|
|
430
|
+
safety: response.safety
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return fail("safety_metadata", "Safety metadata is missing for paid conformance.", {
|
|
434
|
+
errorSafety: error?.safety
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
function retrySafetyCheck(response, error) {
|
|
438
|
+
if (response?.retrySafety) {
|
|
439
|
+
return pass("retry_safety", "Hosted response includes retry safety metadata.", {
|
|
440
|
+
retrySafety: response.retrySafety
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
return fail("retry_safety", "Hosted response retry safety metadata is missing.", {
|
|
444
|
+
errorRetrySafety: error?.retrySafety
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function telemetryMatchesTimelineCheck(response, telemetryEvents) {
|
|
448
|
+
const timelineEventTypes = response?.timeline?.events.map((event) => event.type) ?? [];
|
|
449
|
+
const telemetryEventTypes = telemetryEvents.map((event) => event.type);
|
|
450
|
+
const matches = timelineEventTypes.length > 0 && timelineEventTypes.length === telemetryEventTypes.length && timelineEventTypes.every(
|
|
451
|
+
(eventType, index) => eventType === telemetryEventTypes[index]
|
|
452
|
+
);
|
|
453
|
+
if (matches) {
|
|
454
|
+
return pass("telemetry_matches_timeline", "Telemetry mirrors the hosted timeline.", {
|
|
455
|
+
telemetryEventTypes
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
return fail("telemetry_matches_timeline", "Telemetry does not mirror the hosted timeline.", {
|
|
459
|
+
timelineEventTypes,
|
|
460
|
+
telemetryEventTypes
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function outputSchemaCheck(response, schema) {
|
|
464
|
+
if (!response) {
|
|
465
|
+
return fail("output_schema_valid", "Cannot validate output schema without a response.");
|
|
466
|
+
}
|
|
467
|
+
const result = validateJsonSchemaLikeValue(response.data, schema, {
|
|
468
|
+
path: "$.data"
|
|
469
|
+
});
|
|
470
|
+
if (result.valid) {
|
|
471
|
+
return pass("output_schema_valid", "Provider response data matches the declared schema.");
|
|
472
|
+
}
|
|
473
|
+
return fail("output_schema_valid", "Provider response data does not match the declared schema.", {
|
|
474
|
+
errors: result.errors
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function createSecretSafeOutputCheck(options) {
|
|
478
|
+
const forbiddenOutputValues = options.forbiddenOutputValues.filter(
|
|
479
|
+
(value) => value.length > 0
|
|
480
|
+
);
|
|
481
|
+
const candidate = {
|
|
482
|
+
response: options.response,
|
|
483
|
+
error: options.error,
|
|
484
|
+
telemetryEvents: options.telemetryEvents,
|
|
485
|
+
checks: options.checks,
|
|
486
|
+
summary: createSummary({
|
|
487
|
+
status: statusFromChecks(options.checks),
|
|
488
|
+
checks: options.checks,
|
|
489
|
+
response: options.response,
|
|
490
|
+
error: options.error,
|
|
491
|
+
telemetryEvents: options.telemetryEvents,
|
|
492
|
+
requiredReceiptTypes: options.requiredReceiptTypes
|
|
493
|
+
})
|
|
494
|
+
};
|
|
495
|
+
const serialized = JSON.stringify(candidate);
|
|
496
|
+
const leakedValues = forbiddenOutputValues.filter(
|
|
497
|
+
(value) => serialized.includes(value)
|
|
498
|
+
);
|
|
499
|
+
if (leakedValues.length === 0) {
|
|
500
|
+
return pass("secret_safe_output", "Serialized conformance output contains no forbidden values.", {
|
|
501
|
+
checkedForbiddenValueCount: forbiddenOutputValues.length
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
return fail("secret_safe_output", "Serialized conformance output contains forbidden values.", {
|
|
505
|
+
leakedValueCount: leakedValues.length
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function createSummary(options) {
|
|
509
|
+
const receiptRefs = options.response?.receiptRefs ?? options.error?.receiptRefs;
|
|
510
|
+
const timeline = options.response?.timeline ?? options.error?.timeline;
|
|
511
|
+
const route = options.response?.route ?? options.error?.route;
|
|
512
|
+
const failedCheckNames = options.checks.filter((check) => check.status === "failed").map((check) => check.name);
|
|
513
|
+
return {
|
|
514
|
+
status: options.status,
|
|
515
|
+
totalChecks: options.checks.length,
|
|
516
|
+
passedChecks: options.checks.length - failedCheckNames.length,
|
|
517
|
+
failedChecks: failedCheckNames.length,
|
|
518
|
+
failedCheckNames,
|
|
519
|
+
callId: options.response?.callId ?? options.error?.callId,
|
|
520
|
+
providerId: options.response?.provider.id ?? options.error?.provider,
|
|
521
|
+
errorCode: options.error?.code,
|
|
522
|
+
receiptTypes: getReceiptTypes(receiptRefs),
|
|
523
|
+
requiredReceiptTypes: [...options.requiredReceiptTypes],
|
|
524
|
+
timelineEventTypes: timeline?.events.map((event) => event.type) ?? [],
|
|
525
|
+
telemetryEventTypes: options.telemetryEvents.map((event) => event.type),
|
|
526
|
+
safety: options.response?.safety ?? options.error?.safety,
|
|
527
|
+
retrySafety: options.response?.retrySafety ?? options.error?.retrySafety,
|
|
528
|
+
route: route ? {
|
|
529
|
+
routeId: route.routeId,
|
|
530
|
+
selectedProviderId: route.selectedProviderId,
|
|
531
|
+
fallbackUsed: route.explanation?.fallbackUsed,
|
|
532
|
+
fallbackCount: route.explanation?.fallbackCount,
|
|
533
|
+
stopReason: route.explanation?.stopReason
|
|
534
|
+
} : void 0
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function summarizeReceipts(receiptRefs) {
|
|
538
|
+
const receipts = receiptRefs ?? [];
|
|
539
|
+
const byType = {};
|
|
540
|
+
for (const receiptRef of receipts) {
|
|
541
|
+
byType[receiptRef.type] = (byType[receiptRef.type] ?? 0) + 1;
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
total: receipts.length,
|
|
545
|
+
types: getReceiptTypes(receipts),
|
|
546
|
+
providers: [
|
|
547
|
+
...new Set(receipts.map((receiptRef) => receiptRef.provider))
|
|
548
|
+
],
|
|
549
|
+
byType,
|
|
550
|
+
hasPaymentProof: receipts.some(
|
|
551
|
+
(receiptRef) => receiptRef.type === "payment_proof"
|
|
552
|
+
),
|
|
553
|
+
hasSettlementConfirmation: receipts.some(
|
|
554
|
+
(receiptRef) => receiptRef.type === "settlement_confirmation"
|
|
555
|
+
),
|
|
556
|
+
hasProviderDelivery: receipts.some(
|
|
557
|
+
(receiptRef) => receiptRef.type === "provider_delivery"
|
|
558
|
+
)
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function formatConformanceReportMarkdown(report) {
|
|
562
|
+
const lines = [
|
|
563
|
+
`# ${report.title}`,
|
|
564
|
+
"",
|
|
565
|
+
`Conformance: ${report.status}`,
|
|
566
|
+
`Provider: ${report.summary.providerId ?? "unknown"}`,
|
|
567
|
+
`Call: ${report.summary.callId ?? "unknown"}`,
|
|
568
|
+
`Error: ${report.summary.errorCode ?? "none"}`,
|
|
569
|
+
"",
|
|
570
|
+
"## Checks",
|
|
571
|
+
...report.checks.map(
|
|
572
|
+
(check) => `- [${check.status === "passed" ? "x" : " "}] ${check.name}: ${check.message}`
|
|
573
|
+
),
|
|
574
|
+
"",
|
|
575
|
+
"## Receipts",
|
|
576
|
+
`- Total: ${report.receipts.total}`,
|
|
577
|
+
`- Types: ${formatInlineList(report.receipts.types)}`,
|
|
578
|
+
`- Providers: ${formatInlineList(report.receipts.providers)}`,
|
|
579
|
+
"",
|
|
580
|
+
"## Route",
|
|
581
|
+
`- Route ID: ${report.route?.routeId ?? "none"}`,
|
|
582
|
+
`- Selected provider: ${report.route?.selectedProviderId ?? "none"}`,
|
|
583
|
+
`- Stop reason: ${report.route?.stopReason ?? "none"}`,
|
|
584
|
+
"",
|
|
585
|
+
"## Safety",
|
|
586
|
+
`- Payment: ${report.safety?.paymentStatus ?? "unknown"}`,
|
|
587
|
+
`- Settlement: ${report.safety?.settlementStatus ?? "unknown"}`,
|
|
588
|
+
`- Delivery: ${report.safety?.deliveryStatus ?? "unknown"}`,
|
|
589
|
+
`- Money may have moved: ${String(
|
|
590
|
+
report.safety?.moneyMayHaveMoved ?? false
|
|
591
|
+
)}`,
|
|
592
|
+
"",
|
|
593
|
+
"## Retry Safety",
|
|
594
|
+
`- Retryable: ${String(report.retrySafety?.retryable ?? false)}`,
|
|
595
|
+
`- Safe to auto retry: ${String(
|
|
596
|
+
report.retrySafety?.safeToAutoRetry ?? false
|
|
597
|
+
)}`,
|
|
598
|
+
`- Reason: ${report.retrySafety?.reason ?? "unknown"}`,
|
|
599
|
+
"",
|
|
600
|
+
"## Timeline",
|
|
601
|
+
`- Events: ${formatInlineList(report.timelineEventTypes)}`,
|
|
602
|
+
`- Telemetry: ${formatInlineList(report.telemetryEventTypes)}`
|
|
603
|
+
];
|
|
604
|
+
return `${lines.join("\n")}
|
|
605
|
+
`;
|
|
606
|
+
}
|
|
607
|
+
function schemaForCapability(declaration, capability) {
|
|
608
|
+
if (isSharedJsonSchemaLike(declaration)) {
|
|
609
|
+
return declaration;
|
|
610
|
+
}
|
|
611
|
+
return declaration[capability];
|
|
612
|
+
}
|
|
613
|
+
function toConformanceError(error) {
|
|
614
|
+
if (isProbeMeshError(error)) {
|
|
615
|
+
return {
|
|
616
|
+
name: "ProbeMeshError",
|
|
617
|
+
code: error.code,
|
|
618
|
+
message: error.message,
|
|
619
|
+
status: "failed",
|
|
620
|
+
capability: error.capability,
|
|
621
|
+
provider: error.provider,
|
|
622
|
+
callId: error.callId,
|
|
623
|
+
receiptRefs: error.receiptRefs,
|
|
624
|
+
safety: error.safety,
|
|
625
|
+
timeline: error.timeline,
|
|
626
|
+
retrySafety: error.retrySafety,
|
|
627
|
+
route: error.route
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
name: "ProbeMeshError",
|
|
632
|
+
code: "provider_unavailable",
|
|
633
|
+
message: error instanceof Error ? error.message : "provider conformance call failed.",
|
|
634
|
+
status: "failed"
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function sanitizeConformanceOutput(value, forbiddenOutputValues) {
|
|
638
|
+
const forbiddenValues = forbiddenOutputValues.filter((entry) => entry.length > 0);
|
|
639
|
+
return redactX402Secrets(
|
|
640
|
+
replaceForbiddenValues(value, forbiddenValues, /* @__PURE__ */ new WeakSet())
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
function replaceForbiddenValues(value, forbiddenValues, seen) {
|
|
644
|
+
if (typeof value === "string") {
|
|
645
|
+
return forbiddenValues.reduce(
|
|
646
|
+
(current, forbidden) => current.split(forbidden).join("[REDACTED]"),
|
|
647
|
+
value
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
if (value === null || typeof value !== "object" || value instanceof Date) {
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
if (seen.has(value)) {
|
|
654
|
+
return "[Circular]";
|
|
655
|
+
}
|
|
656
|
+
seen.add(value);
|
|
657
|
+
if (Array.isArray(value)) {
|
|
658
|
+
const entries2 = value.map(
|
|
659
|
+
(entry) => replaceForbiddenValues(entry, forbiddenValues, seen)
|
|
660
|
+
);
|
|
661
|
+
seen.delete(value);
|
|
662
|
+
return entries2;
|
|
663
|
+
}
|
|
664
|
+
const entries = Object.fromEntries(
|
|
665
|
+
Object.entries(value).map(([key, entry]) => [
|
|
666
|
+
key,
|
|
667
|
+
replaceForbiddenValues(entry, forbiddenValues, seen)
|
|
668
|
+
])
|
|
669
|
+
);
|
|
670
|
+
seen.delete(value);
|
|
671
|
+
return entries;
|
|
672
|
+
}
|
|
673
|
+
function assertConformanceOptions(options) {
|
|
674
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
675
|
+
throw new ProbeMeshError({
|
|
676
|
+
code: "invalid_request",
|
|
677
|
+
message: "Provider conformance options must be an object."
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if (!Array.isArray(options.adapters)) {
|
|
681
|
+
throw new ProbeMeshError({
|
|
682
|
+
code: "invalid_request",
|
|
683
|
+
message: "Provider conformance adapters must be an array."
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
if (!options.request || typeof options.request !== "object" || Array.isArray(options.request)) {
|
|
687
|
+
throw new ProbeMeshError({
|
|
688
|
+
code: "invalid_request",
|
|
689
|
+
message: "Provider conformance request must be an object."
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function getReceiptTypes(receiptRefs) {
|
|
694
|
+
return [...new Set((receiptRefs ?? []).map((receiptRef) => receiptRef.type))];
|
|
695
|
+
}
|
|
696
|
+
function statusFromChecks(checks) {
|
|
697
|
+
return checks.every((check) => check.status === "passed") ? "passed" : "failed";
|
|
698
|
+
}
|
|
699
|
+
function formatInlineList(values) {
|
|
700
|
+
return values.length > 0 ? values.join(", ") : "none";
|
|
701
|
+
}
|
|
702
|
+
function pass(name, message, details) {
|
|
703
|
+
return {
|
|
704
|
+
name,
|
|
705
|
+
status: "passed",
|
|
706
|
+
message,
|
|
707
|
+
details
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function fail(name, message, details) {
|
|
711
|
+
return {
|
|
712
|
+
name,
|
|
713
|
+
status: "failed",
|
|
714
|
+
message,
|
|
715
|
+
details
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/cli/providerConformanceCli.ts
|
|
720
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
721
|
+
import { dirname, resolve } from "path";
|
|
722
|
+
var USAGE = "Usage: probemesh-conformance --config <path> [--format markdown|json] [--out-json <path>] [--out-md <path>] [--out-artifact <path>] [--title <text>] [--artifact-id <text>] [--generated-at <iso>]";
|
|
723
|
+
async function runProviderConformanceCli(options = {}) {
|
|
724
|
+
const argv = options.argv ?? [];
|
|
725
|
+
const cwd = options.cwd ?? process.cwd();
|
|
726
|
+
try {
|
|
727
|
+
const parsedArgs = parseCliArgs(argv);
|
|
728
|
+
if (parsedArgs.help) {
|
|
729
|
+
return {
|
|
730
|
+
exitCode: 0,
|
|
731
|
+
skipped: true,
|
|
732
|
+
stdout: `${USAGE}
|
|
733
|
+
`
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
if (!parsedArgs.configPath) {
|
|
737
|
+
return cliFailure("--config <path> is required.");
|
|
738
|
+
}
|
|
739
|
+
const configExport = await loadCliConfig(parsedArgs.configPath, {
|
|
740
|
+
argv,
|
|
741
|
+
cwd
|
|
742
|
+
});
|
|
743
|
+
if (isCliSkip(configExport)) {
|
|
744
|
+
return {
|
|
745
|
+
exitCode: 0,
|
|
746
|
+
skipped: true,
|
|
747
|
+
skip: configExport,
|
|
748
|
+
stdout: `provider conformance skipped: ${configExport.reason}
|
|
749
|
+
`
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
assertCliConfig(configExport);
|
|
753
|
+
const result = await runProviderConformance(configExport.conformance);
|
|
754
|
+
const report = createProviderConformanceReport(result, {
|
|
755
|
+
...configExport.report ?? {},
|
|
756
|
+
title: parsedArgs.title ?? configExport.report?.title
|
|
757
|
+
});
|
|
758
|
+
const outputs = {
|
|
759
|
+
json: parsedArgs.outJson ?? configExport.outputs?.json,
|
|
760
|
+
markdown: parsedArgs.outMarkdown ?? configExport.outputs?.markdown,
|
|
761
|
+
artifact: parsedArgs.outArtifact ?? configExport.outputs?.artifact
|
|
762
|
+
};
|
|
763
|
+
const artifact = createOptionalArtifact({
|
|
764
|
+
config: configExport,
|
|
765
|
+
report,
|
|
766
|
+
artifactId: parsedArgs.artifactId,
|
|
767
|
+
generatedAt: parsedArgs.generatedAt,
|
|
768
|
+
requestedArtifactOutput: outputs.artifact
|
|
769
|
+
});
|
|
770
|
+
const selectedFormat = parsedArgs.format ?? configExport.report?.format ?? "markdown";
|
|
771
|
+
const stdout = formatProviderConformanceReport(report, {
|
|
772
|
+
format: selectedFormat
|
|
773
|
+
});
|
|
774
|
+
const artifactPaths = await writeArtifacts({
|
|
775
|
+
cwd,
|
|
776
|
+
report,
|
|
777
|
+
artifact,
|
|
778
|
+
outputs
|
|
779
|
+
});
|
|
780
|
+
return {
|
|
781
|
+
exitCode: result.status === "passed" ? 0 : 1,
|
|
782
|
+
skipped: false,
|
|
783
|
+
result,
|
|
784
|
+
report,
|
|
785
|
+
artifact,
|
|
786
|
+
stdout,
|
|
787
|
+
artifactPaths
|
|
788
|
+
};
|
|
789
|
+
} catch (error) {
|
|
790
|
+
return cliFailure(
|
|
791
|
+
error instanceof Error ? error.message : "Unknown provider conformance CLI failure."
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function parseCliArgs(argv) {
|
|
796
|
+
const parsedArgs = {};
|
|
797
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
798
|
+
const arg = argv[index];
|
|
799
|
+
if (arg === "--") {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (arg === "--help" || arg === "-h") {
|
|
803
|
+
parsedArgs.help = true;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (arg === "--config") {
|
|
807
|
+
parsedArgs.configPath = requireValue(argv, index, arg);
|
|
808
|
+
index += 1;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (arg === "--format") {
|
|
812
|
+
const format = requireValue(argv, index, arg);
|
|
813
|
+
if (format !== "markdown" && format !== "json") {
|
|
814
|
+
throw new Error("--format must be markdown or json.");
|
|
815
|
+
}
|
|
816
|
+
parsedArgs.format = format;
|
|
817
|
+
index += 1;
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (arg === "--out-json") {
|
|
821
|
+
parsedArgs.outJson = requireValue(argv, index, arg);
|
|
822
|
+
index += 1;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (arg === "--out-md") {
|
|
826
|
+
parsedArgs.outMarkdown = requireValue(argv, index, arg);
|
|
827
|
+
index += 1;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (arg === "--out-artifact") {
|
|
831
|
+
parsedArgs.outArtifact = requireValue(argv, index, arg);
|
|
832
|
+
index += 1;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if (arg === "--title") {
|
|
836
|
+
parsedArgs.title = requireValue(argv, index, arg);
|
|
837
|
+
index += 1;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (arg === "--artifact-id") {
|
|
841
|
+
parsedArgs.artifactId = requireValue(argv, index, arg);
|
|
842
|
+
index += 1;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (arg === "--generated-at") {
|
|
846
|
+
parsedArgs.generatedAt = requireValue(argv, index, arg);
|
|
847
|
+
index += 1;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
throw new Error(`Unknown argument "${arg}".`);
|
|
851
|
+
}
|
|
852
|
+
return parsedArgs;
|
|
853
|
+
}
|
|
854
|
+
function requireValue(argv, index, flag) {
|
|
855
|
+
const value = argv[index + 1];
|
|
856
|
+
if (!value || value.startsWith("--")) {
|
|
857
|
+
throw new Error(`${flag} requires a value.`);
|
|
858
|
+
}
|
|
859
|
+
return value;
|
|
860
|
+
}
|
|
861
|
+
async function loadCliConfig(configPath, context) {
|
|
862
|
+
const configExport = await loadCliConfigDefault(configPath, context.cwd);
|
|
863
|
+
if (typeof configExport === "function") {
|
|
864
|
+
return configExport(context);
|
|
865
|
+
}
|
|
866
|
+
return configExport;
|
|
867
|
+
}
|
|
868
|
+
function isCliSkip(value) {
|
|
869
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && value.skip === true && typeof value.reason === "string";
|
|
870
|
+
}
|
|
871
|
+
function assertCliConfig(config) {
|
|
872
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
873
|
+
throw new Error("Provider conformance CLI config must be an object.");
|
|
874
|
+
}
|
|
875
|
+
const candidate = config;
|
|
876
|
+
if (!candidate.conformance || typeof candidate.conformance !== "object" || Array.isArray(candidate.conformance)) {
|
|
877
|
+
throw new Error("Provider conformance CLI config requires conformance options.");
|
|
878
|
+
}
|
|
879
|
+
if (candidate.report !== void 0 && !isRecord2(candidate.report)) {
|
|
880
|
+
throw new Error("Provider conformance CLI report config must be an object.");
|
|
881
|
+
}
|
|
882
|
+
if (candidate.artifact !== void 0 && !isRecord2(candidate.artifact)) {
|
|
883
|
+
throw new Error("Provider conformance CLI artifact config must be an object.");
|
|
884
|
+
}
|
|
885
|
+
if (candidate.outputs !== void 0) {
|
|
886
|
+
assertOutputs(candidate.outputs);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
function assertOutputs(outputs) {
|
|
890
|
+
if (!isRecord2(outputs)) {
|
|
891
|
+
throw new Error("Provider conformance CLI outputs must be an object.");
|
|
892
|
+
}
|
|
893
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
894
|
+
if (!["json", "markdown", "artifact"].includes(key) || value !== void 0 && typeof value !== "string") {
|
|
895
|
+
throw new Error(
|
|
896
|
+
"Provider conformance CLI outputs may only include json, markdown, and artifact string paths."
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function createOptionalArtifact(options) {
|
|
902
|
+
if (!options.config.artifact && !options.requestedArtifactOutput) {
|
|
903
|
+
return void 0;
|
|
904
|
+
}
|
|
905
|
+
return createProviderConformanceArtifact({
|
|
906
|
+
manifest: options.config.conformance.manifest,
|
|
907
|
+
report: options.report,
|
|
908
|
+
artifactId: options.artifactId ?? options.config.artifact?.artifactId,
|
|
909
|
+
generatedAt: options.generatedAt ?? options.config.artifact?.generatedAt,
|
|
910
|
+
forbiddenOutputValues: options.config.artifact?.forbiddenOutputValues ?? options.config.report?.forbiddenOutputValues ?? options.config.conformance.forbiddenOutputValues
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
async function writeArtifacts(options) {
|
|
914
|
+
const artifactPaths = {};
|
|
915
|
+
if (options.outputs.json) {
|
|
916
|
+
const path = resolve(options.cwd, options.outputs.json);
|
|
917
|
+
await writeTextFile(
|
|
918
|
+
path,
|
|
919
|
+
formatProviderConformanceReport(options.report, {
|
|
920
|
+
format: "json"
|
|
921
|
+
})
|
|
922
|
+
);
|
|
923
|
+
artifactPaths.json = path;
|
|
924
|
+
}
|
|
925
|
+
if (options.outputs.markdown) {
|
|
926
|
+
const path = resolve(options.cwd, options.outputs.markdown);
|
|
927
|
+
await writeTextFile(
|
|
928
|
+
path,
|
|
929
|
+
formatProviderConformanceReport(options.report, {
|
|
930
|
+
format: "markdown"
|
|
931
|
+
})
|
|
932
|
+
);
|
|
933
|
+
artifactPaths.markdown = path;
|
|
934
|
+
}
|
|
935
|
+
if (options.outputs.artifact) {
|
|
936
|
+
if (!options.artifact) {
|
|
937
|
+
throw new Error("Provider conformance artifact output requested but no artifact was created.");
|
|
938
|
+
}
|
|
939
|
+
const path = resolve(options.cwd, options.outputs.artifact);
|
|
940
|
+
await writeTextFile(
|
|
941
|
+
path,
|
|
942
|
+
formatProviderConformanceArtifact(options.artifact, {
|
|
943
|
+
format: "json"
|
|
944
|
+
})
|
|
945
|
+
);
|
|
946
|
+
artifactPaths.artifact = path;
|
|
947
|
+
}
|
|
948
|
+
return artifactPaths;
|
|
949
|
+
}
|
|
950
|
+
async function writeTextFile(path, body) {
|
|
951
|
+
await mkdir(dirname(path), {
|
|
952
|
+
recursive: true
|
|
953
|
+
});
|
|
954
|
+
await writeFile(path, body);
|
|
955
|
+
}
|
|
956
|
+
function cliFailure(message) {
|
|
957
|
+
return {
|
|
958
|
+
exitCode: 2,
|
|
959
|
+
skipped: false,
|
|
960
|
+
stdout: "",
|
|
961
|
+
stderr: `Provider conformance CLI failed: ${message}
|
|
962
|
+
`
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
function isRecord2(value) {
|
|
966
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
export {
|
|
970
|
+
validateJsonSchemaLikeValue,
|
|
971
|
+
assertJsonSchemaLikeValue,
|
|
972
|
+
runProviderConformance,
|
|
973
|
+
createProviderConformanceReport,
|
|
974
|
+
formatProviderConformanceReport,
|
|
975
|
+
runProviderConformanceCli
|
|
976
|
+
};
|
|
977
|
+
//# sourceMappingURL=chunk-WKN7QOCA.js.map
|