@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,2978 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/cli/providerDossierCheckCli.ts
|
|
5
|
+
var import_promises2 = require("fs/promises");
|
|
6
|
+
var import_node_path2 = require("path");
|
|
7
|
+
|
|
8
|
+
// src/cli/configLoader.ts
|
|
9
|
+
var import_promises = require("fs/promises");
|
|
10
|
+
var import_node_path = require("path");
|
|
11
|
+
var import_node_url = require("url");
|
|
12
|
+
async function loadCliConfigDefault(configPath, cwd) {
|
|
13
|
+
const resolvedPath = (0, import_node_path.resolve)(cwd, configPath);
|
|
14
|
+
const moduleUrl = (0, import_node_url.pathToFileURL)(resolvedPath);
|
|
15
|
+
try {
|
|
16
|
+
const loadedModule = await import(
|
|
17
|
+
/* @vite-ignore */
|
|
18
|
+
moduleUrl.href
|
|
19
|
+
);
|
|
20
|
+
return loadedModule.default;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return loadSelfContainedConfigFallback(resolvedPath, error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function loadSelfContainedConfigFallback(resolvedPath, importError) {
|
|
26
|
+
const source = await (0, import_promises.readFile)(resolvedPath, "utf8");
|
|
27
|
+
if (/\bimport\s/.test(source) || !/\bexport\s+default\b/.test(source)) {
|
|
28
|
+
throw importError;
|
|
29
|
+
}
|
|
30
|
+
const transformedSource = source.replace(
|
|
31
|
+
/\bexport\s+default\b/,
|
|
32
|
+
"const __probemeshDefault ="
|
|
33
|
+
);
|
|
34
|
+
try {
|
|
35
|
+
const factory = new Function(
|
|
36
|
+
`${transformedSource}
|
|
37
|
+
return __probemeshDefault;`
|
|
38
|
+
);
|
|
39
|
+
return factory();
|
|
40
|
+
} catch {
|
|
41
|
+
throw importError;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/errors.ts
|
|
46
|
+
var ProbeMeshError = class extends Error {
|
|
47
|
+
code;
|
|
48
|
+
status = "failed";
|
|
49
|
+
capability;
|
|
50
|
+
provider;
|
|
51
|
+
callId;
|
|
52
|
+
receiptRefs;
|
|
53
|
+
safety;
|
|
54
|
+
timeline;
|
|
55
|
+
retrySafety;
|
|
56
|
+
attempt;
|
|
57
|
+
route;
|
|
58
|
+
cause;
|
|
59
|
+
constructor(options) {
|
|
60
|
+
super(options.message);
|
|
61
|
+
this.name = "ProbeMeshError";
|
|
62
|
+
this.code = options.code;
|
|
63
|
+
this.capability = options.capability;
|
|
64
|
+
this.provider = options.provider;
|
|
65
|
+
this.callId = options.callId;
|
|
66
|
+
this.receiptRefs = options.receiptRefs;
|
|
67
|
+
this.safety = options.safety;
|
|
68
|
+
this.timeline = options.timeline;
|
|
69
|
+
this.retrySafety = options.retrySafety;
|
|
70
|
+
this.attempt = options.attempt;
|
|
71
|
+
this.route = options.route;
|
|
72
|
+
this.cause = options.cause;
|
|
73
|
+
}
|
|
74
|
+
toJSON() {
|
|
75
|
+
return {
|
|
76
|
+
name: this.name,
|
|
77
|
+
code: this.code,
|
|
78
|
+
message: this.message,
|
|
79
|
+
status: this.status,
|
|
80
|
+
capability: this.capability,
|
|
81
|
+
provider: this.provider,
|
|
82
|
+
callId: this.callId,
|
|
83
|
+
receiptRefs: this.receiptRefs,
|
|
84
|
+
safety: this.safety,
|
|
85
|
+
timeline: this.timeline,
|
|
86
|
+
retrySafety: this.retrySafety,
|
|
87
|
+
attempt: this.attempt,
|
|
88
|
+
route: this.route
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/paymentOptions.ts
|
|
94
|
+
function resolveProviderPaymentOption(input) {
|
|
95
|
+
const options = getProviderPaymentOptions(input);
|
|
96
|
+
const candidates = options.filter((option) => optionSupportsCapability(option, input.capability)).map((option, index) => ({
|
|
97
|
+
option,
|
|
98
|
+
index,
|
|
99
|
+
cost: pricingToCost(resolvePaymentOptionPricing(option, input.capability) ?? resolvePricing(input.pricing, input.capability)),
|
|
100
|
+
receiptTypes: option.receiptTypes ?? []
|
|
101
|
+
}));
|
|
102
|
+
const filtered = candidates.filter(
|
|
103
|
+
({ option, receiptTypes }) => optionMatchesPaymentPreferences(option, receiptTypes, input.paymentPreferences)
|
|
104
|
+
);
|
|
105
|
+
if (filtered.length === 0) {
|
|
106
|
+
return {
|
|
107
|
+
options,
|
|
108
|
+
reason: createNoPaymentOptionReason(input, candidates.length)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const selected = choosePaymentOption(
|
|
112
|
+
filtered,
|
|
113
|
+
input.paymentPreferences,
|
|
114
|
+
input.paymentStrategy
|
|
115
|
+
);
|
|
116
|
+
return {
|
|
117
|
+
options,
|
|
118
|
+
selection: {
|
|
119
|
+
providerId: input.providerId,
|
|
120
|
+
optionId: selected.option.id,
|
|
121
|
+
protocolMode: selected.option.protocolMode,
|
|
122
|
+
cost: selected.cost,
|
|
123
|
+
receiptTypes: [...selected.receiptTypes],
|
|
124
|
+
score: selected.score,
|
|
125
|
+
scoreReasons: [...selected.scoreReasons],
|
|
126
|
+
metadata: cloneSerializable(selected.option.metadata)
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function getProviderPaymentOptions(input) {
|
|
131
|
+
if (input.paymentOptions && input.paymentOptions.length > 0) {
|
|
132
|
+
return cloneSerializable(input.paymentOptions);
|
|
133
|
+
}
|
|
134
|
+
return [
|
|
135
|
+
{
|
|
136
|
+
id: "default",
|
|
137
|
+
protocolMode: input.providerMode ?? "local",
|
|
138
|
+
capabilities: [...input.capabilities],
|
|
139
|
+
...input.pricing !== void 0 ? { pricing: cloneSerializable(input.pricing) } : {},
|
|
140
|
+
receiptTypes: [...input.defaultReceiptTypes ?? []]
|
|
141
|
+
}
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
function validateProviderPaymentOptions(paymentOptions, path, capabilities, errors) {
|
|
145
|
+
if (paymentOptions === void 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!Array.isArray(paymentOptions) || paymentOptions.length === 0) {
|
|
149
|
+
errors.push({
|
|
150
|
+
path,
|
|
151
|
+
message: "paymentOptions must be a non-empty array when provided."
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const seen = /* @__PURE__ */ new Set();
|
|
156
|
+
paymentOptions.forEach((option, index) => {
|
|
157
|
+
const optionPath = `${path}.${index}`;
|
|
158
|
+
if (!isRecord(option)) {
|
|
159
|
+
errors.push({
|
|
160
|
+
path: optionPath,
|
|
161
|
+
message: "payment option must be an object."
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
validateNonEmptyString(option.id, `${optionPath}.id`, errors);
|
|
166
|
+
validateNonEmptyString(option.protocolMode, `${optionPath}.protocolMode`, errors);
|
|
167
|
+
if (typeof option.id === "string") {
|
|
168
|
+
if (seen.has(option.id)) {
|
|
169
|
+
errors.push({
|
|
170
|
+
path: `${optionPath}.id`,
|
|
171
|
+
message: `duplicate payment option "${option.id}".`
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
seen.add(option.id);
|
|
175
|
+
}
|
|
176
|
+
if (option.capabilities !== void 0) {
|
|
177
|
+
validateStringArray(option.capabilities, `${optionPath}.capabilities`, errors);
|
|
178
|
+
if (Array.isArray(option.capabilities)) {
|
|
179
|
+
for (const capability of option.capabilities) {
|
|
180
|
+
if (typeof capability === "string" && capability.length > 0 && !capabilities.includes(capability)) {
|
|
181
|
+
errors.push({
|
|
182
|
+
path: `${optionPath}.capabilities`,
|
|
183
|
+
message: `payment option capability "${capability}" is not declared by the provider.`
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (option.pricing !== void 0) {
|
|
190
|
+
validatePricingDeclaration(option.pricing, `${optionPath}.pricing`, errors);
|
|
191
|
+
}
|
|
192
|
+
if (option.receiptTypes !== void 0) {
|
|
193
|
+
if (!Array.isArray(option.receiptTypes) || option.receiptTypes.some((receiptType) => !isReceiptType(receiptType))) {
|
|
194
|
+
errors.push({
|
|
195
|
+
path: `${optionPath}.receiptTypes`,
|
|
196
|
+
message: "payment option receiptTypes must contain known receipt types."
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (option.metadata !== void 0 && !isRecord(option.metadata)) {
|
|
201
|
+
errors.push({
|
|
202
|
+
path: `${optionPath}.metadata`,
|
|
203
|
+
message: "payment option metadata must be an object when provided."
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function validatePaymentPreferences(paymentPreferences, capability) {
|
|
209
|
+
if (paymentPreferences === void 0) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!isRecord(paymentPreferences)) {
|
|
213
|
+
throw new ProbeMeshError({
|
|
214
|
+
code: "invalid_request",
|
|
215
|
+
message: "paymentPreferences must be an object when provided.",
|
|
216
|
+
capability
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
validatePreferenceStringArray(
|
|
220
|
+
paymentPreferences.allowedProtocols,
|
|
221
|
+
"allowedProtocols",
|
|
222
|
+
capability
|
|
223
|
+
);
|
|
224
|
+
validatePreferenceStringArray(
|
|
225
|
+
paymentPreferences.preferredProtocols,
|
|
226
|
+
"preferredProtocols",
|
|
227
|
+
capability
|
|
228
|
+
);
|
|
229
|
+
validatePreferenceStringArray(
|
|
230
|
+
paymentPreferences.blockedProtocols,
|
|
231
|
+
"blockedProtocols",
|
|
232
|
+
capability
|
|
233
|
+
);
|
|
234
|
+
validatePreferenceStringArray(
|
|
235
|
+
paymentPreferences.allowedPaymentOptions,
|
|
236
|
+
"allowedPaymentOptions",
|
|
237
|
+
capability
|
|
238
|
+
);
|
|
239
|
+
validatePreferenceStringArray(
|
|
240
|
+
paymentPreferences.preferredPaymentOptions,
|
|
241
|
+
"preferredPaymentOptions",
|
|
242
|
+
capability
|
|
243
|
+
);
|
|
244
|
+
validatePreferenceStringArray(
|
|
245
|
+
paymentPreferences.blockedPaymentOptions,
|
|
246
|
+
"blockedPaymentOptions",
|
|
247
|
+
capability
|
|
248
|
+
);
|
|
249
|
+
if (paymentPreferences.requiredReceiptTypes !== void 0 && (!Array.isArray(paymentPreferences.requiredReceiptTypes) || paymentPreferences.requiredReceiptTypes.some((receiptType) => !isReceiptType(receiptType)))) {
|
|
250
|
+
throw new ProbeMeshError({
|
|
251
|
+
code: "invalid_request",
|
|
252
|
+
message: "paymentPreferences.requiredReceiptTypes must contain known receipt types.",
|
|
253
|
+
capability
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function validatePaymentStrategy(paymentStrategy, capability) {
|
|
258
|
+
if (paymentStrategy === void 0) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!isRecord(paymentStrategy)) {
|
|
262
|
+
throw new ProbeMeshError({
|
|
263
|
+
code: "invalid_request",
|
|
264
|
+
message: "paymentStrategy must be an object when provided.",
|
|
265
|
+
capability
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (!isPaymentStrategyMode(paymentStrategy.mode)) {
|
|
269
|
+
throw new ProbeMeshError({
|
|
270
|
+
code: "invalid_request",
|
|
271
|
+
message: 'paymentStrategy.mode must be one of "provider_order", "lowest_cost", "preferred_protocol", or "receipt_coverage".',
|
|
272
|
+
capability
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (paymentStrategy.preferredReceiptTypes !== void 0 && (!Array.isArray(paymentStrategy.preferredReceiptTypes) || paymentStrategy.preferredReceiptTypes.some(
|
|
276
|
+
(receiptType) => !isReceiptType(receiptType)
|
|
277
|
+
))) {
|
|
278
|
+
throw new ProbeMeshError({
|
|
279
|
+
code: "invalid_request",
|
|
280
|
+
message: "paymentStrategy.preferredReceiptTypes must contain known receipt types.",
|
|
281
|
+
capability
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function mergePaymentPreferences(first, second) {
|
|
286
|
+
if (!first && !second) {
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
return compactPaymentPreferences({
|
|
290
|
+
allowedProtocols: intersectArrays(first?.allowedProtocols, second?.allowedProtocols),
|
|
291
|
+
preferredProtocols: second?.preferredProtocols ?? first?.preferredProtocols,
|
|
292
|
+
blockedProtocols: unionArrays(first?.blockedProtocols, second?.blockedProtocols),
|
|
293
|
+
allowedPaymentOptions: intersectArrays(
|
|
294
|
+
first?.allowedPaymentOptions,
|
|
295
|
+
second?.allowedPaymentOptions
|
|
296
|
+
),
|
|
297
|
+
preferredPaymentOptions: second?.preferredPaymentOptions ?? first?.preferredPaymentOptions,
|
|
298
|
+
blockedPaymentOptions: unionArrays(
|
|
299
|
+
first?.blockedPaymentOptions,
|
|
300
|
+
second?.blockedPaymentOptions
|
|
301
|
+
),
|
|
302
|
+
requiredReceiptTypes: unionArrays(
|
|
303
|
+
first?.requiredReceiptTypes,
|
|
304
|
+
second?.requiredReceiptTypes
|
|
305
|
+
)
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function optionSupportsCapability(option, capability) {
|
|
309
|
+
return !option.capabilities || option.capabilities.includes(capability);
|
|
310
|
+
}
|
|
311
|
+
function optionMatchesPaymentPreferences(option, receiptTypes, preferences) {
|
|
312
|
+
if (!preferences) {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
if (preferences.allowedProtocols && !preferences.allowedProtocols.includes(option.protocolMode)) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
if (preferences.blockedProtocols?.includes(option.protocolMode)) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
if (preferences.allowedPaymentOptions && !preferences.allowedPaymentOptions.includes(option.id)) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
if (preferences.blockedPaymentOptions?.includes(option.id)) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
const receiptSet = new Set(receiptTypes);
|
|
328
|
+
return (preferences.requiredReceiptTypes ?? []).every(
|
|
329
|
+
(receiptType) => receiptSet.has(receiptType)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
function choosePaymentOption(candidates, preferences, strategy) {
|
|
333
|
+
const mode = strategy?.mode ?? "provider_order";
|
|
334
|
+
const ranked = [...candidates].sort(
|
|
335
|
+
(left, right) => comparePaymentOptions(left, right, preferences, strategy)
|
|
336
|
+
);
|
|
337
|
+
return ranked.map((candidate, rank) => ({
|
|
338
|
+
...candidate,
|
|
339
|
+
score: rank,
|
|
340
|
+
scoreReasons: createScoreReasons(candidate, preferences, strategy, mode)
|
|
341
|
+
}))[0];
|
|
342
|
+
}
|
|
343
|
+
function preferenceIndex(preferences, value) {
|
|
344
|
+
const index = preferences.indexOf(value);
|
|
345
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
|
346
|
+
}
|
|
347
|
+
function comparePaymentOptions(left, right, preferences, strategy) {
|
|
348
|
+
switch (strategy?.mode ?? "provider_order") {
|
|
349
|
+
case "lowest_cost":
|
|
350
|
+
return compareCost(left, right) || left.index - right.index;
|
|
351
|
+
case "preferred_protocol":
|
|
352
|
+
return comparePreferredProtocol(left, right, preferences) || compareCost(left, right) || left.index - right.index;
|
|
353
|
+
case "receipt_coverage":
|
|
354
|
+
return compareReceiptCoverage(left, right, preferences, strategy) || comparePreferredProtocol(left, right, preferences) || compareCost(left, right) || left.index - right.index;
|
|
355
|
+
case "provider_order":
|
|
356
|
+
return compareProviderOrder(left, right, preferences);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function compareProviderOrder(left, right, preferences) {
|
|
360
|
+
const optionPreference = preferences?.preferredPaymentOptions ?? [];
|
|
361
|
+
const protocolPreference = preferences?.preferredProtocols ?? [];
|
|
362
|
+
const optionPreferenceDelta = preferenceIndex(optionPreference, left.option.id) - preferenceIndex(optionPreference, right.option.id);
|
|
363
|
+
if (optionPreferenceDelta !== 0) {
|
|
364
|
+
return optionPreferenceDelta;
|
|
365
|
+
}
|
|
366
|
+
const protocolPreferenceDelta = preferenceIndex(protocolPreference, left.option.protocolMode) - preferenceIndex(protocolPreference, right.option.protocolMode);
|
|
367
|
+
if (protocolPreferenceDelta !== 0) {
|
|
368
|
+
return protocolPreferenceDelta;
|
|
369
|
+
}
|
|
370
|
+
return left.index - right.index;
|
|
371
|
+
}
|
|
372
|
+
function comparePreferredProtocol(left, right, preferences) {
|
|
373
|
+
const protocolPreference = preferences?.preferredProtocols ?? [];
|
|
374
|
+
return preferenceIndex(protocolPreference, left.option.protocolMode) - preferenceIndex(protocolPreference, right.option.protocolMode);
|
|
375
|
+
}
|
|
376
|
+
function compareCost(left, right) {
|
|
377
|
+
const leftCost = costSortValue(left.cost);
|
|
378
|
+
const rightCost = costSortValue(right.cost);
|
|
379
|
+
if (leftCost === rightCost) {
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
return leftCost < rightCost ? -1 : 1;
|
|
383
|
+
}
|
|
384
|
+
function compareReceiptCoverage(left, right, preferences, strategy) {
|
|
385
|
+
return receiptCoverage(right, preferences, strategy) - receiptCoverage(left, preferences, strategy);
|
|
386
|
+
}
|
|
387
|
+
function receiptCoverage(candidate, preferences, strategy) {
|
|
388
|
+
const desiredReceiptTypes = strategy?.preferredReceiptTypes ?? preferences?.requiredReceiptTypes ?? [];
|
|
389
|
+
const receiptSet = new Set(candidate.receiptTypes);
|
|
390
|
+
if (desiredReceiptTypes.length > 0) {
|
|
391
|
+
return desiredReceiptTypes.filter((receiptType) => receiptSet.has(receiptType)).length;
|
|
392
|
+
}
|
|
393
|
+
return receiptSet.size;
|
|
394
|
+
}
|
|
395
|
+
function costSortValue(cost) {
|
|
396
|
+
return cost?.amountUsd ?? Number.POSITIVE_INFINITY;
|
|
397
|
+
}
|
|
398
|
+
function createScoreReasons(candidate, preferences, strategy, mode) {
|
|
399
|
+
const reasons = [`strategy:${mode}`];
|
|
400
|
+
if (mode === "provider_order") {
|
|
401
|
+
const preferredOptionIndex = preferenceIndex(
|
|
402
|
+
preferences?.preferredPaymentOptions ?? [],
|
|
403
|
+
candidate.option.id
|
|
404
|
+
);
|
|
405
|
+
const preferredProtocolIndex = preferenceIndex(
|
|
406
|
+
preferences?.preferredProtocols ?? [],
|
|
407
|
+
candidate.option.protocolMode
|
|
408
|
+
);
|
|
409
|
+
if (preferredOptionIndex !== Number.MAX_SAFE_INTEGER) {
|
|
410
|
+
reasons.push(`preferred_option:${candidate.option.id}`);
|
|
411
|
+
} else if (preferredProtocolIndex !== Number.MAX_SAFE_INTEGER) {
|
|
412
|
+
reasons.push(`preferred_protocol:${candidate.option.protocolMode}`);
|
|
413
|
+
} else {
|
|
414
|
+
reasons.push(`provider_order:${candidate.index}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (mode === "lowest_cost" || mode === "preferred_protocol" || mode === "receipt_coverage") {
|
|
418
|
+
reasons.push(
|
|
419
|
+
candidate.cost ? `cost:${candidate.cost.amountUsd}` : "cost:unknown"
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (mode === "preferred_protocol") {
|
|
423
|
+
reasons.push(
|
|
424
|
+
isPreferredProtocol(candidate, preferences) ? `preferred_protocol:${candidate.option.protocolMode}` : `protocol:${candidate.option.protocolMode}`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
if (mode === "receipt_coverage") {
|
|
428
|
+
const desiredReceiptTypes = strategy?.preferredReceiptTypes ?? preferences?.requiredReceiptTypes ?? [];
|
|
429
|
+
reasons.push(
|
|
430
|
+
desiredReceiptTypes.length > 0 ? `receipt_coverage:${receiptCoverage(candidate, preferences, strategy)}/${desiredReceiptTypes.length}` : `receipt_coverage:${candidate.receiptTypes.length}`
|
|
431
|
+
);
|
|
432
|
+
if (isPreferredProtocol(candidate, preferences)) {
|
|
433
|
+
reasons.push(`preferred_protocol:${candidate.option.protocolMode}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return reasons;
|
|
437
|
+
}
|
|
438
|
+
function isPreferredProtocol(candidate, preferences) {
|
|
439
|
+
return preferences?.preferredProtocols?.includes(candidate.option.protocolMode) ?? false;
|
|
440
|
+
}
|
|
441
|
+
function createNoPaymentOptionReason(input, capabilityCandidateCount) {
|
|
442
|
+
if (capabilityCandidateCount === 0) {
|
|
443
|
+
return `Provider "${input.providerId}" has no payment option for capability "${input.capability}".`;
|
|
444
|
+
}
|
|
445
|
+
return `Provider "${input.providerId}" has no payment option matching paymentPreferences for capability "${input.capability}".`;
|
|
446
|
+
}
|
|
447
|
+
function resolvePaymentOptionPricing(option, capability) {
|
|
448
|
+
return resolvePricing(option.pricing, capability);
|
|
449
|
+
}
|
|
450
|
+
function resolvePricing(pricing, capability) {
|
|
451
|
+
if (!pricing) {
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
if (isPricing(pricing)) {
|
|
455
|
+
return pricing;
|
|
456
|
+
}
|
|
457
|
+
const capabilityPricing = pricing[capability];
|
|
458
|
+
return isPricing(capabilityPricing) ? capabilityPricing : void 0;
|
|
459
|
+
}
|
|
460
|
+
function pricingToCost(pricing) {
|
|
461
|
+
if (!pricing) {
|
|
462
|
+
return void 0;
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
amountUsd: pricing.amountUsd,
|
|
466
|
+
currency: pricing.currency ?? "USD",
|
|
467
|
+
unit: pricing.unit
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function validatePreferenceStringArray(value, field, capability) {
|
|
471
|
+
if (value !== void 0 && (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0))) {
|
|
472
|
+
throw new ProbeMeshError({
|
|
473
|
+
code: "invalid_request",
|
|
474
|
+
message: `paymentPreferences.${field} must be an array of non-empty strings when provided.`,
|
|
475
|
+
capability
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function validatePricingDeclaration(pricing, path, errors) {
|
|
480
|
+
if (!isRecord(pricing)) {
|
|
481
|
+
errors.push({
|
|
482
|
+
path,
|
|
483
|
+
message: "payment option pricing must be an object."
|
|
484
|
+
});
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (isPricing(pricing)) {
|
|
488
|
+
validatePricing(pricing, path, errors);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
for (const [capability, capabilityPricing] of Object.entries(pricing)) {
|
|
492
|
+
validatePricing(capabilityPricing, `${path}.${capability}`, errors);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function validatePricing(pricing, path, errors) {
|
|
496
|
+
if (!isRecord(pricing)) {
|
|
497
|
+
errors.push({
|
|
498
|
+
path,
|
|
499
|
+
message: "payment option pricing entry must be an object."
|
|
500
|
+
});
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
validateNonEmptyString(pricing.unit, `${path}.unit`, errors);
|
|
504
|
+
if (typeof pricing.amountUsd !== "number" || !Number.isFinite(pricing.amountUsd) || pricing.amountUsd < 0) {
|
|
505
|
+
errors.push({
|
|
506
|
+
path: `${path}.amountUsd`,
|
|
507
|
+
message: "amountUsd must be a non-negative number."
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (pricing.currency !== void 0 && typeof pricing.currency !== "string") {
|
|
511
|
+
errors.push({
|
|
512
|
+
path: `${path}.currency`,
|
|
513
|
+
message: "currency must be a string when provided."
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function validateStringArray(value, path, errors) {
|
|
518
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
519
|
+
errors.push({
|
|
520
|
+
path,
|
|
521
|
+
message: `${path} must be an array of non-empty strings.`
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function validateNonEmptyString(value, path, errors) {
|
|
526
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
527
|
+
errors.push({
|
|
528
|
+
path,
|
|
529
|
+
message: `${path} must be a non-empty string.`
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function isPricing(value) {
|
|
534
|
+
return isRecord(value) && typeof value.unit === "string" && value.unit.length > 0 && typeof value.amountUsd === "number" && Number.isFinite(value.amountUsd) && value.amountUsd >= 0 && (value.currency === void 0 || typeof value.currency === "string");
|
|
535
|
+
}
|
|
536
|
+
function isReceiptType(value) {
|
|
537
|
+
return value === "payment_proof" || value === "settlement_confirmation" || value === "provider_delivery" || value === "provider_response_evidence" || value === "authorization_decision";
|
|
538
|
+
}
|
|
539
|
+
function isPaymentStrategyMode(value) {
|
|
540
|
+
return value === "provider_order" || value === "lowest_cost" || value === "preferred_protocol" || value === "receipt_coverage";
|
|
541
|
+
}
|
|
542
|
+
function compactPaymentPreferences(preferences) {
|
|
543
|
+
const entries = Object.entries(preferences).filter(
|
|
544
|
+
([, value]) => value !== void 0 && (!Array.isArray(value) || value.length > 0)
|
|
545
|
+
);
|
|
546
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
547
|
+
}
|
|
548
|
+
function intersectArrays(first, second) {
|
|
549
|
+
if (first === void 0) {
|
|
550
|
+
return second === void 0 ? void 0 : [...second];
|
|
551
|
+
}
|
|
552
|
+
if (second === void 0) {
|
|
553
|
+
return [...first];
|
|
554
|
+
}
|
|
555
|
+
const secondValues = new Set(second);
|
|
556
|
+
return first.filter((value) => secondValues.has(value));
|
|
557
|
+
}
|
|
558
|
+
function unionArrays(first, second) {
|
|
559
|
+
const values = [...first ?? [], ...second ?? []];
|
|
560
|
+
return values.length > 0 ? [...new Set(values)] : void 0;
|
|
561
|
+
}
|
|
562
|
+
function isRecord(value) {
|
|
563
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
564
|
+
}
|
|
565
|
+
function cloneSerializable(value) {
|
|
566
|
+
if (value === void 0) {
|
|
567
|
+
return value;
|
|
568
|
+
}
|
|
569
|
+
return JSON.parse(JSON.stringify(value));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/providerKit/manifest.ts
|
|
573
|
+
var JSON_SCHEMA_HINT_KEYS = /* @__PURE__ */ new Set([
|
|
574
|
+
"$schema",
|
|
575
|
+
"$id",
|
|
576
|
+
"type",
|
|
577
|
+
"properties",
|
|
578
|
+
"required",
|
|
579
|
+
"items",
|
|
580
|
+
"enum",
|
|
581
|
+
"description",
|
|
582
|
+
"additionalProperties",
|
|
583
|
+
"oneOf",
|
|
584
|
+
"anyOf",
|
|
585
|
+
"allOf"
|
|
586
|
+
]);
|
|
587
|
+
function validateProviderManifest(manifest) {
|
|
588
|
+
const errors = [];
|
|
589
|
+
if (!isRecord2(manifest)) {
|
|
590
|
+
return {
|
|
591
|
+
valid: false,
|
|
592
|
+
errors: [
|
|
593
|
+
{
|
|
594
|
+
path: "$",
|
|
595
|
+
message: "Provider manifest must be an object."
|
|
596
|
+
}
|
|
597
|
+
]
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
validateNonEmptyString2(manifest.id, "id", errors);
|
|
601
|
+
validateNonEmptyString2(manifest.displayName, "displayName", errors);
|
|
602
|
+
validateCapabilityList(manifest.capabilities, errors);
|
|
603
|
+
validateNonEmptyString2(manifest.protocolMode, "protocolMode", errors);
|
|
604
|
+
validateProviderPaymentOptions(
|
|
605
|
+
manifest.paymentOptions,
|
|
606
|
+
"paymentOptions",
|
|
607
|
+
Array.isArray(manifest.capabilities) ? manifest.capabilities : [],
|
|
608
|
+
errors
|
|
609
|
+
);
|
|
610
|
+
validatePricingDeclaration2(
|
|
611
|
+
manifest.pricing,
|
|
612
|
+
"pricing",
|
|
613
|
+
manifest.capabilities,
|
|
614
|
+
errors
|
|
615
|
+
);
|
|
616
|
+
validateSchemaDeclaration(
|
|
617
|
+
manifest.inputSchema,
|
|
618
|
+
"inputSchema",
|
|
619
|
+
manifest.capabilities,
|
|
620
|
+
errors
|
|
621
|
+
);
|
|
622
|
+
validateSchemaDeclaration(
|
|
623
|
+
manifest.outputSchema,
|
|
624
|
+
"outputSchema",
|
|
625
|
+
manifest.capabilities,
|
|
626
|
+
errors
|
|
627
|
+
);
|
|
628
|
+
validateReceipts(manifest.receipts, errors);
|
|
629
|
+
if (manifest.limits !== void 0) {
|
|
630
|
+
validateLimits(manifest.limits, errors);
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
valid: errors.length === 0,
|
|
634
|
+
errors
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function isSharedJsonSchemaLike(value) {
|
|
638
|
+
if (!isRecord2(value)) {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return Object.keys(value).some((key) => JSON_SCHEMA_HINT_KEYS.has(key));
|
|
642
|
+
}
|
|
643
|
+
function isSharedPricing(value) {
|
|
644
|
+
return isRecord2(value) && typeof value.unit === "string" && value.unit.length > 0 && typeof value.amountUsd === "number";
|
|
645
|
+
}
|
|
646
|
+
function validateCapabilityList(capabilities, errors) {
|
|
647
|
+
if (!Array.isArray(capabilities) || capabilities.length === 0) {
|
|
648
|
+
errors.push({
|
|
649
|
+
path: "capabilities",
|
|
650
|
+
message: "capabilities must be a non-empty array."
|
|
651
|
+
});
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const seen = /* @__PURE__ */ new Set();
|
|
655
|
+
capabilities.forEach((capability, index) => {
|
|
656
|
+
if (typeof capability !== "string" || capability.length === 0) {
|
|
657
|
+
errors.push({
|
|
658
|
+
path: `capabilities.${index}`,
|
|
659
|
+
message: "capability ids must be non-empty strings."
|
|
660
|
+
});
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (seen.has(capability)) {
|
|
664
|
+
errors.push({
|
|
665
|
+
path: `capabilities.${index}`,
|
|
666
|
+
message: `duplicate capability "${capability}".`
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
seen.add(capability);
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
function validatePricingDeclaration2(pricing, path, capabilities, errors) {
|
|
673
|
+
if (!isRecord2(pricing)) {
|
|
674
|
+
errors.push({
|
|
675
|
+
path,
|
|
676
|
+
message: "pricing must be an object."
|
|
677
|
+
});
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (isSharedPricing(pricing)) {
|
|
681
|
+
validatePricing2(pricing, path, errors);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (!Array.isArray(capabilities)) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
for (const capability of capabilities) {
|
|
688
|
+
if (typeof capability !== "string" || capability.length === 0) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
validatePricing2(pricing[capability], `${path}.${capability}`, errors);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function validatePricing2(pricing, path, errors) {
|
|
695
|
+
if (!isRecord2(pricing)) {
|
|
696
|
+
errors.push({
|
|
697
|
+
path,
|
|
698
|
+
message: "pricing entry must be an object."
|
|
699
|
+
});
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
validateNonEmptyString2(pricing.unit, `${path}.unit`, errors);
|
|
703
|
+
if (typeof pricing.amountUsd !== "number" || !Number.isFinite(pricing.amountUsd) || pricing.amountUsd < 0) {
|
|
704
|
+
errors.push({
|
|
705
|
+
path: `${path}.amountUsd`,
|
|
706
|
+
message: "amountUsd must be a non-negative number."
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
if (pricing.currency !== void 0 && typeof pricing.currency !== "string") {
|
|
710
|
+
errors.push({
|
|
711
|
+
path: `${path}.currency`,
|
|
712
|
+
message: "currency must be a string when provided."
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function validateSchemaDeclaration(schema, path, capabilities, errors) {
|
|
717
|
+
if (!isRecord2(schema)) {
|
|
718
|
+
errors.push({
|
|
719
|
+
path,
|
|
720
|
+
message: `${path} must be an object.`
|
|
721
|
+
});
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (isSharedJsonSchemaLike(schema)) {
|
|
725
|
+
validateSchemaLike(schema, path, errors);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (!Array.isArray(capabilities)) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
for (const capability of capabilities) {
|
|
732
|
+
if (typeof capability !== "string" || capability.length === 0) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
validateSchemaLike(schema[capability], `${path}.${capability}`, errors);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function validateSchemaLike(schema, path, errors) {
|
|
739
|
+
if (!isRecord2(schema)) {
|
|
740
|
+
errors.push({
|
|
741
|
+
path,
|
|
742
|
+
message: "schema entry must be an object."
|
|
743
|
+
});
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (schema.type !== void 0 && typeof schema.type !== "string") {
|
|
747
|
+
errors.push({
|
|
748
|
+
path: `${path}.type`,
|
|
749
|
+
message: "schema type must be a string when provided."
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
if (schema.properties !== void 0 && !isRecord2(schema.properties)) {
|
|
753
|
+
errors.push({
|
|
754
|
+
path: `${path}.properties`,
|
|
755
|
+
message: "schema properties must be an object when provided."
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
if (schema.required !== void 0 && (!Array.isArray(schema.required) || schema.required.some((field) => typeof field !== "string"))) {
|
|
759
|
+
errors.push({
|
|
760
|
+
path: `${path}.required`,
|
|
761
|
+
message: "schema required must be an array of strings when provided."
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function validateReceipts(receipts, errors) {
|
|
766
|
+
if (!isRecord2(receipts)) {
|
|
767
|
+
errors.push({
|
|
768
|
+
path: "receipts",
|
|
769
|
+
message: "receipts must be an object."
|
|
770
|
+
});
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
validateBoolean(receipts.payment, "receipts.payment", errors);
|
|
774
|
+
validateBoolean(receipts.delivery, "receipts.delivery", errors);
|
|
775
|
+
validateBoolean(
|
|
776
|
+
receipts.responseEvidence,
|
|
777
|
+
"receipts.responseEvidence",
|
|
778
|
+
errors
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
function validateLimits(limits, errors) {
|
|
782
|
+
if (!isRecord2(limits)) {
|
|
783
|
+
errors.push({
|
|
784
|
+
path: "limits",
|
|
785
|
+
message: "limits must be an object when provided."
|
|
786
|
+
});
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const typedLimits = limits;
|
|
790
|
+
if (typedLimits.maxRequestsPerMinute !== void 0 && (!Number.isFinite(typedLimits.maxRequestsPerMinute) || typedLimits.maxRequestsPerMinute <= 0)) {
|
|
791
|
+
errors.push({
|
|
792
|
+
path: "limits.maxRequestsPerMinute",
|
|
793
|
+
message: "maxRequestsPerMinute must be a positive number when provided."
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function validateNonEmptyString2(value, path, errors) {
|
|
798
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
799
|
+
errors.push({
|
|
800
|
+
path,
|
|
801
|
+
message: `${path} must be a non-empty string.`
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function validateBoolean(value, path, errors) {
|
|
806
|
+
if (typeof value !== "boolean") {
|
|
807
|
+
errors.push({
|
|
808
|
+
path,
|
|
809
|
+
message: `${path} must be a boolean.`
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function isRecord2(value) {
|
|
814
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/protocols/x402Redaction.ts
|
|
818
|
+
var DEFAULT_REPLACEMENT = "[REDACTED]";
|
|
819
|
+
var DEFAULT_MAX_DEPTH = 12;
|
|
820
|
+
var PRIVATE_KEY_PATTERN = /\b0x[a-fA-F0-9]{64}\b/g;
|
|
821
|
+
var HEX_SIGNATURE_PATTERN = /\b0x[a-fA-F0-9]{130}\b/g;
|
|
822
|
+
var SECRET_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
823
|
+
"api-key",
|
|
824
|
+
"apikey",
|
|
825
|
+
"authorization",
|
|
826
|
+
"bearer",
|
|
827
|
+
"payment-required",
|
|
828
|
+
"payment-response",
|
|
829
|
+
"payment-signature",
|
|
830
|
+
"privatekey",
|
|
831
|
+
"signature",
|
|
832
|
+
"x-api-key"
|
|
833
|
+
]);
|
|
834
|
+
function redactX402Secrets(value, options = {}) {
|
|
835
|
+
const replacement = options.replacement ?? DEFAULT_REPLACEMENT;
|
|
836
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
837
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
838
|
+
return redactValue(value, {
|
|
839
|
+
replacement,
|
|
840
|
+
maxDepth,
|
|
841
|
+
depth: 0,
|
|
842
|
+
seen,
|
|
843
|
+
key: void 0
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
function redactValue(value, context) {
|
|
847
|
+
if (context.key && shouldRedactKey(context.key)) {
|
|
848
|
+
return context.replacement;
|
|
849
|
+
}
|
|
850
|
+
if (typeof value === "string") {
|
|
851
|
+
return redactString(value, context.replacement);
|
|
852
|
+
}
|
|
853
|
+
if (value === null || typeof value !== "object" || value instanceof Date) {
|
|
854
|
+
return value;
|
|
855
|
+
}
|
|
856
|
+
if (context.depth >= context.maxDepth) {
|
|
857
|
+
return "[MaxDepth]";
|
|
858
|
+
}
|
|
859
|
+
if (context.seen.has(value)) {
|
|
860
|
+
return "[Circular]";
|
|
861
|
+
}
|
|
862
|
+
context.seen.add(value);
|
|
863
|
+
if (Array.isArray(value)) {
|
|
864
|
+
return value.map(
|
|
865
|
+
(entry) => redactValue(entry, {
|
|
866
|
+
...context,
|
|
867
|
+
depth: context.depth + 1,
|
|
868
|
+
key: void 0
|
|
869
|
+
})
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
return Object.fromEntries(
|
|
873
|
+
Object.entries(value).map(([key, entry]) => [
|
|
874
|
+
key,
|
|
875
|
+
redactValue(entry, {
|
|
876
|
+
...context,
|
|
877
|
+
depth: context.depth + 1,
|
|
878
|
+
key
|
|
879
|
+
})
|
|
880
|
+
])
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
function shouldRedactKey(key) {
|
|
884
|
+
const normalized = key.toLowerCase().replace(/_/g, "-");
|
|
885
|
+
return SECRET_FIELD_NAMES.has(normalized) || normalized.includes("private-key") || normalized.includes("privatekey") || normalized.includes("payment-signature") || normalized.includes("payment-response") || normalized.includes("payment-required") || normalized.includes("api-key");
|
|
886
|
+
}
|
|
887
|
+
function redactString(value, replacement) {
|
|
888
|
+
return value.replace(PRIVATE_KEY_PATTERN, replacement).replace(HEX_SIGNATURE_PATTERN, replacement);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/providerCatalogArtifact.ts
|
|
892
|
+
var PROVIDER_CATALOG_ARTIFACT_SCHEMA_VERSION = "probemesh.provider-catalog.v1";
|
|
893
|
+
function validateProviderCatalogArtifact(artifact) {
|
|
894
|
+
const errors = [];
|
|
895
|
+
if (!isRecord3(artifact)) {
|
|
896
|
+
return {
|
|
897
|
+
valid: false,
|
|
898
|
+
errors: [
|
|
899
|
+
{
|
|
900
|
+
path: "$",
|
|
901
|
+
message: "Provider catalog artifact must be an object."
|
|
902
|
+
}
|
|
903
|
+
]
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
if (artifact.schemaVersion !== PROVIDER_CATALOG_ARTIFACT_SCHEMA_VERSION) {
|
|
907
|
+
errors.push({
|
|
908
|
+
path: "schemaVersion",
|
|
909
|
+
message: `schemaVersion must be "${PROVIDER_CATALOG_ARTIFACT_SCHEMA_VERSION}".`
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
validateNonEmptyString3(artifact.artifactId, "artifactId", errors);
|
|
913
|
+
validateIsoTimestamp(artifact.generatedAt, "generatedAt", errors);
|
|
914
|
+
if (artifact.status !== "ready" && artifact.status !== "rejected") {
|
|
915
|
+
errors.push({
|
|
916
|
+
path: "status",
|
|
917
|
+
message: 'status must be "ready" or "rejected".'
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
const manifestValidation = validateProviderManifest(artifact.provider);
|
|
921
|
+
for (const manifestError of manifestValidation.errors) {
|
|
922
|
+
errors.push({
|
|
923
|
+
path: `provider.${manifestError.path}`,
|
|
924
|
+
message: manifestError.message
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
validateAcceptanceArtifact(artifact.acceptance, errors);
|
|
928
|
+
return {
|
|
929
|
+
valid: errors.length === 0,
|
|
930
|
+
errors
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
function validateAcceptanceArtifact(acceptance, errors) {
|
|
934
|
+
if (!isRecord3(acceptance)) {
|
|
935
|
+
errors.push({
|
|
936
|
+
path: "acceptance",
|
|
937
|
+
message: "acceptance must be an object."
|
|
938
|
+
});
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
validateNonEmptyString3(acceptance.title, "acceptance.title", errors);
|
|
942
|
+
if (acceptance.status !== "passed" && acceptance.status !== "failed") {
|
|
943
|
+
errors.push({
|
|
944
|
+
path: "acceptance.status",
|
|
945
|
+
message: 'acceptance.status must be "passed" or "failed".'
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
if (!isRecord3(acceptance.gate)) {
|
|
949
|
+
errors.push({
|
|
950
|
+
path: "acceptance.gate",
|
|
951
|
+
message: "acceptance.gate must be an object."
|
|
952
|
+
});
|
|
953
|
+
} else {
|
|
954
|
+
if (typeof acceptance.gate.accepted !== "boolean") {
|
|
955
|
+
errors.push({
|
|
956
|
+
path: "acceptance.gate.accepted",
|
|
957
|
+
message: "acceptance.gate.accepted must be a boolean."
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
if (acceptance.gate.status !== "accepted" && acceptance.gate.status !== "rejected") {
|
|
961
|
+
errors.push({
|
|
962
|
+
path: "acceptance.gate.status",
|
|
963
|
+
message: 'acceptance.gate.status must be "accepted" or "rejected".'
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
validateStringArray2(
|
|
968
|
+
acceptance.failedChecks,
|
|
969
|
+
"acceptance.failedChecks",
|
|
970
|
+
errors
|
|
971
|
+
);
|
|
972
|
+
validateReceiptSummary(acceptance.receipts, errors);
|
|
973
|
+
validateStringArray2(
|
|
974
|
+
acceptance.timelineEventTypes,
|
|
975
|
+
"acceptance.timelineEventTypes",
|
|
976
|
+
errors
|
|
977
|
+
);
|
|
978
|
+
validateStringArray2(
|
|
979
|
+
acceptance.telemetryEventTypes,
|
|
980
|
+
"acceptance.telemetryEventTypes",
|
|
981
|
+
errors
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
function validateReceiptSummary(receipts, errors) {
|
|
985
|
+
if (!isRecord3(receipts)) {
|
|
986
|
+
errors.push({
|
|
987
|
+
path: "acceptance.receipts",
|
|
988
|
+
message: "acceptance.receipts must be an object."
|
|
989
|
+
});
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (typeof receipts.total !== "number" || !Number.isFinite(receipts.total)) {
|
|
993
|
+
errors.push({
|
|
994
|
+
path: "acceptance.receipts.total",
|
|
995
|
+
message: "acceptance.receipts.total must be a number."
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
validateStringArray2(receipts.types, "acceptance.receipts.types", errors);
|
|
999
|
+
validateStringArray2(
|
|
1000
|
+
receipts.providers,
|
|
1001
|
+
"acceptance.receipts.providers",
|
|
1002
|
+
errors
|
|
1003
|
+
);
|
|
1004
|
+
for (const key of [
|
|
1005
|
+
"hasPaymentProof",
|
|
1006
|
+
"hasSettlementConfirmation",
|
|
1007
|
+
"hasProviderDelivery"
|
|
1008
|
+
]) {
|
|
1009
|
+
if (typeof receipts[key] !== "boolean") {
|
|
1010
|
+
errors.push({
|
|
1011
|
+
path: `acceptance.receipts.${key}`,
|
|
1012
|
+
message: `acceptance.receipts.${key} must be a boolean.`
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function validateStringArray2(value, path, errors) {
|
|
1018
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
1019
|
+
errors.push({
|
|
1020
|
+
path,
|
|
1021
|
+
message: `${path} must be an array of strings.`
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function validateNonEmptyString3(value, path, errors) {
|
|
1026
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1027
|
+
errors.push({
|
|
1028
|
+
path,
|
|
1029
|
+
message: `${path} must be a non-empty string.`
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function validateIsoTimestamp(value, path, errors) {
|
|
1034
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
1035
|
+
errors.push({
|
|
1036
|
+
path,
|
|
1037
|
+
message: `${path} must be an ISO timestamp string.`
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function isRecord3(value) {
|
|
1042
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/providerCatalog.ts
|
|
1046
|
+
var PROVIDER_CATALOG_SCHEMA_VERSION = "probemesh.provider-catalog.index.v1";
|
|
1047
|
+
function validateProviderCatalog(catalog) {
|
|
1048
|
+
const errors = [];
|
|
1049
|
+
if (!isRecord4(catalog)) {
|
|
1050
|
+
return {
|
|
1051
|
+
valid: false,
|
|
1052
|
+
errors: [
|
|
1053
|
+
{
|
|
1054
|
+
path: "$",
|
|
1055
|
+
message: "Provider catalog must be an object."
|
|
1056
|
+
}
|
|
1057
|
+
]
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
if (catalog.schemaVersion !== PROVIDER_CATALOG_SCHEMA_VERSION) {
|
|
1061
|
+
errors.push({
|
|
1062
|
+
path: "schemaVersion",
|
|
1063
|
+
message: `schemaVersion must be "${PROVIDER_CATALOG_SCHEMA_VERSION}".`
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
validateIsoTimestamp2(catalog.generatedAt, "generatedAt", errors);
|
|
1067
|
+
if (!Array.isArray(catalog.entries)) {
|
|
1068
|
+
errors.push({
|
|
1069
|
+
path: "entries",
|
|
1070
|
+
message: "entries must be an array."
|
|
1071
|
+
});
|
|
1072
|
+
} else {
|
|
1073
|
+
catalog.entries.forEach(
|
|
1074
|
+
(entry, index) => validateProviderCatalogEntry(entry, `entries.${index}`, errors)
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
valid: errors.length === 0,
|
|
1079
|
+
errors
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
function assertProviderCatalog(catalog) {
|
|
1083
|
+
const result = validateProviderCatalog(catalog);
|
|
1084
|
+
if (result.valid) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
throw new ProbeMeshError({
|
|
1088
|
+
code: "invalid_request",
|
|
1089
|
+
message: `Invalid provider catalog: ${formatValidationErrors(result.errors)}`
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
function createProviderCatalog(options) {
|
|
1093
|
+
assertProviderCatalogOptions(options);
|
|
1094
|
+
const entries = options.artifacts.map((artifact, index) => {
|
|
1095
|
+
const validation = validateProviderCatalogArtifact(artifact);
|
|
1096
|
+
if (!validation.valid) {
|
|
1097
|
+
throw new ProbeMeshError({
|
|
1098
|
+
code: "invalid_request",
|
|
1099
|
+
message: `Invalid provider catalog artifact at index ${index}: ${formatValidationErrors(
|
|
1100
|
+
validation.errors
|
|
1101
|
+
)}`
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
return createCatalogEntryFromArtifact(artifact);
|
|
1105
|
+
});
|
|
1106
|
+
const catalog = {
|
|
1107
|
+
schemaVersion: PROVIDER_CATALOG_SCHEMA_VERSION,
|
|
1108
|
+
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1109
|
+
entries
|
|
1110
|
+
};
|
|
1111
|
+
assertProviderCatalog(catalog);
|
|
1112
|
+
return catalog;
|
|
1113
|
+
}
|
|
1114
|
+
function findProviderCatalogMatches(catalog, query) {
|
|
1115
|
+
assertProviderCatalog(catalog);
|
|
1116
|
+
assertProviderCatalogQuery(query);
|
|
1117
|
+
const requiredStatus = query.status ?? "ready";
|
|
1118
|
+
const requiredReceiptTypes = query.requiredReceiptTypes ?? [];
|
|
1119
|
+
const providerIds = query.providerIds;
|
|
1120
|
+
const blockedProviderIds = new Set(query.blockedProviderIds ?? []);
|
|
1121
|
+
return catalog.entries.flatMap((entry) => {
|
|
1122
|
+
const providerId = entry.provider.id;
|
|
1123
|
+
if (entry.status !== requiredStatus) {
|
|
1124
|
+
return [];
|
|
1125
|
+
}
|
|
1126
|
+
if (!entry.provider.capabilities.includes(query.capability)) {
|
|
1127
|
+
return [];
|
|
1128
|
+
}
|
|
1129
|
+
if (query.protocolMode !== void 0 && entry.provider.protocolMode !== query.protocolMode) {
|
|
1130
|
+
return [];
|
|
1131
|
+
}
|
|
1132
|
+
if (providerIds !== void 0 && !providerIds.includes(providerId)) {
|
|
1133
|
+
return [];
|
|
1134
|
+
}
|
|
1135
|
+
if (blockedProviderIds.has(providerId)) {
|
|
1136
|
+
return [];
|
|
1137
|
+
}
|
|
1138
|
+
const paymentPreferences = mergePaymentPreferences(
|
|
1139
|
+
query.protocolMode ? {
|
|
1140
|
+
allowedProtocols: [query.protocolMode]
|
|
1141
|
+
} : void 0,
|
|
1142
|
+
query.paymentPreferences
|
|
1143
|
+
);
|
|
1144
|
+
const paymentResolution = resolveProviderPaymentOption({
|
|
1145
|
+
providerId,
|
|
1146
|
+
providerMode: entry.provider.protocolMode,
|
|
1147
|
+
capabilities: entry.provider.capabilities,
|
|
1148
|
+
capability: query.capability,
|
|
1149
|
+
pricing: entry.provider.pricing,
|
|
1150
|
+
paymentOptions: entry.provider.paymentOptions,
|
|
1151
|
+
paymentPreferences,
|
|
1152
|
+
paymentStrategy: query.paymentStrategy,
|
|
1153
|
+
defaultReceiptTypes: entry.acceptance.receipts.types
|
|
1154
|
+
});
|
|
1155
|
+
const cost = paymentSelectionCost(paymentResolution.selection) ?? resolveCatalogCost(entry, query.capability);
|
|
1156
|
+
if (query.maxCostUsd !== void 0 && (cost === void 0 || cost.amountUsd > query.maxCostUsd)) {
|
|
1157
|
+
return [];
|
|
1158
|
+
}
|
|
1159
|
+
if (!hasRequiredReceiptTypes(entry, requiredReceiptTypes)) {
|
|
1160
|
+
return [];
|
|
1161
|
+
}
|
|
1162
|
+
if (!paymentResolution.selection) {
|
|
1163
|
+
return [];
|
|
1164
|
+
}
|
|
1165
|
+
return [
|
|
1166
|
+
{
|
|
1167
|
+
entry,
|
|
1168
|
+
providerId,
|
|
1169
|
+
capability: query.capability,
|
|
1170
|
+
status: entry.status,
|
|
1171
|
+
cost,
|
|
1172
|
+
paymentSelection: paymentResolution.selection,
|
|
1173
|
+
reasons: createMatchReasons(
|
|
1174
|
+
entry,
|
|
1175
|
+
query,
|
|
1176
|
+
cost,
|
|
1177
|
+
paymentResolution.selection
|
|
1178
|
+
)
|
|
1179
|
+
}
|
|
1180
|
+
];
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
function summarizeProviderCatalog(catalog) {
|
|
1184
|
+
assertProviderCatalog(catalog);
|
|
1185
|
+
const providers = /* @__PURE__ */ new Set();
|
|
1186
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
1187
|
+
const protocolModes = /* @__PURE__ */ new Set();
|
|
1188
|
+
let readyEntries = 0;
|
|
1189
|
+
let rejectedEntries = 0;
|
|
1190
|
+
for (const entry of catalog.entries) {
|
|
1191
|
+
providers.add(entry.provider.id);
|
|
1192
|
+
protocolModes.add(entry.provider.protocolMode);
|
|
1193
|
+
for (const capability of entry.provider.capabilities) {
|
|
1194
|
+
capabilities.add(capability);
|
|
1195
|
+
}
|
|
1196
|
+
if (entry.status === "ready") {
|
|
1197
|
+
readyEntries += 1;
|
|
1198
|
+
} else {
|
|
1199
|
+
rejectedEntries += 1;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return {
|
|
1203
|
+
schemaVersion: catalog.schemaVersion,
|
|
1204
|
+
generatedAt: catalog.generatedAt,
|
|
1205
|
+
totalEntries: catalog.entries.length,
|
|
1206
|
+
readyEntries,
|
|
1207
|
+
rejectedEntries,
|
|
1208
|
+
providers: [...providers],
|
|
1209
|
+
capabilities: [...capabilities],
|
|
1210
|
+
protocolModes: [...protocolModes]
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function assertProviderCatalogOptions(options) {
|
|
1214
|
+
if (!isRecord4(options)) {
|
|
1215
|
+
throw new ProbeMeshError({
|
|
1216
|
+
code: "invalid_request",
|
|
1217
|
+
message: "Provider catalog options must be an object."
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
if (!Array.isArray(options.artifacts)) {
|
|
1221
|
+
throw new ProbeMeshError({
|
|
1222
|
+
code: "invalid_request",
|
|
1223
|
+
message: "Provider catalog options require an artifacts array."
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
if (options.generatedAt !== void 0 && (typeof options.generatedAt !== "string" || Number.isNaN(Date.parse(options.generatedAt)))) {
|
|
1227
|
+
throw new ProbeMeshError({
|
|
1228
|
+
code: "invalid_request",
|
|
1229
|
+
message: "Provider catalog generatedAt must be an ISO timestamp."
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
function assertProviderCatalogQuery(query) {
|
|
1234
|
+
if (!isRecord4(query)) {
|
|
1235
|
+
throw new ProbeMeshError({
|
|
1236
|
+
code: "invalid_request",
|
|
1237
|
+
message: "Provider catalog query must be an object."
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
if (typeof query.capability !== "string" || query.capability.length === 0) {
|
|
1241
|
+
throw new ProbeMeshError({
|
|
1242
|
+
code: "invalid_request",
|
|
1243
|
+
message: "Provider catalog query capability must be a non-empty string."
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (query.protocolMode !== void 0 && (typeof query.protocolMode !== "string" || query.protocolMode.length === 0)) {
|
|
1247
|
+
throw new ProbeMeshError({
|
|
1248
|
+
code: "invalid_request",
|
|
1249
|
+
message: "Provider catalog query protocolMode must be a non-empty string."
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
validatePaymentPreferences(query.paymentPreferences, query.capability);
|
|
1253
|
+
validatePaymentStrategy(query.paymentStrategy, query.capability);
|
|
1254
|
+
if (query.maxCostUsd !== void 0 && (typeof query.maxCostUsd !== "number" || !Number.isFinite(query.maxCostUsd) || query.maxCostUsd < 0)) {
|
|
1255
|
+
throw new ProbeMeshError({
|
|
1256
|
+
code: "invalid_request",
|
|
1257
|
+
message: "Provider catalog query maxCostUsd must be a non-negative finite number."
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
if (query.status !== void 0 && query.status !== "ready" && query.status !== "rejected") {
|
|
1261
|
+
throw new ProbeMeshError({
|
|
1262
|
+
code: "invalid_request",
|
|
1263
|
+
message: 'Provider catalog query status must be "ready" or "rejected".'
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
validateQueryStringArray(query.providerIds, "providerIds");
|
|
1267
|
+
validateQueryStringArray(query.blockedProviderIds, "blockedProviderIds");
|
|
1268
|
+
if (query.requiredReceiptTypes !== void 0 && (!Array.isArray(query.requiredReceiptTypes) || query.requiredReceiptTypes.some((receiptType) => !isReceiptType2(receiptType)))) {
|
|
1269
|
+
throw new ProbeMeshError({
|
|
1270
|
+
code: "invalid_request",
|
|
1271
|
+
message: "Provider catalog query requiredReceiptTypes must contain known receipt types."
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function createCatalogEntryFromArtifact(artifact) {
|
|
1276
|
+
return {
|
|
1277
|
+
artifactId: artifact.artifactId,
|
|
1278
|
+
generatedAt: artifact.generatedAt,
|
|
1279
|
+
status: artifact.status,
|
|
1280
|
+
provider: {
|
|
1281
|
+
id: artifact.provider.id,
|
|
1282
|
+
displayName: artifact.provider.displayName,
|
|
1283
|
+
capabilities: [...artifact.provider.capabilities],
|
|
1284
|
+
protocolMode: artifact.provider.protocolMode,
|
|
1285
|
+
paymentOptions: cloneSerializable2(artifact.provider.paymentOptions),
|
|
1286
|
+
pricing: cloneSerializable2(artifact.provider.pricing),
|
|
1287
|
+
receipts: cloneSerializable2(artifact.provider.receipts),
|
|
1288
|
+
limits: cloneSerializable2(artifact.provider.limits)
|
|
1289
|
+
},
|
|
1290
|
+
acceptance: {
|
|
1291
|
+
gate: cloneSerializable2(artifact.acceptance.gate),
|
|
1292
|
+
failedChecks: [...artifact.acceptance.failedChecks],
|
|
1293
|
+
receipts: cloneSerializable2(artifact.acceptance.receipts),
|
|
1294
|
+
safety: cloneSerializable2(artifact.acceptance.safety),
|
|
1295
|
+
retrySafety: cloneSerializable2(artifact.acceptance.retrySafety),
|
|
1296
|
+
route: cloneSerializable2(artifact.acceptance.route),
|
|
1297
|
+
timelineEventTypes: [...artifact.acceptance.timelineEventTypes],
|
|
1298
|
+
telemetryEventTypes: [...artifact.acceptance.telemetryEventTypes],
|
|
1299
|
+
errorCode: artifact.acceptance.errorCode
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
function resolveCatalogCost(entry, capability) {
|
|
1304
|
+
const pricing = entry.provider.pricing;
|
|
1305
|
+
if (isProbeMeshPricing(pricing)) {
|
|
1306
|
+
return pricingToCost2(pricing);
|
|
1307
|
+
}
|
|
1308
|
+
const capabilityPricing = pricing[capability];
|
|
1309
|
+
if (!isProbeMeshPricing(capabilityPricing)) {
|
|
1310
|
+
return void 0;
|
|
1311
|
+
}
|
|
1312
|
+
return pricingToCost2(capabilityPricing);
|
|
1313
|
+
}
|
|
1314
|
+
function pricingToCost2(pricing) {
|
|
1315
|
+
return {
|
|
1316
|
+
amountUsd: pricing.amountUsd,
|
|
1317
|
+
currency: pricing.currency,
|
|
1318
|
+
unit: pricing.unit
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
function hasRequiredReceiptTypes(entry, requiredReceiptTypes) {
|
|
1322
|
+
const receiptTypes = new Set(entry.acceptance.receipts.types);
|
|
1323
|
+
return requiredReceiptTypes.every(
|
|
1324
|
+
(receiptType) => receiptTypes.has(receiptType)
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
function createMatchReasons(entry, query, cost, paymentSelection) {
|
|
1328
|
+
const reasons = [
|
|
1329
|
+
`status:${entry.status}`,
|
|
1330
|
+
`capability:${query.capability}`,
|
|
1331
|
+
`protocol:${entry.provider.protocolMode}`
|
|
1332
|
+
];
|
|
1333
|
+
if (query.paymentPreferences !== void 0) {
|
|
1334
|
+
reasons.push("payment_preferences:matched");
|
|
1335
|
+
}
|
|
1336
|
+
if (query.paymentStrategy !== void 0) {
|
|
1337
|
+
reasons.push(`payment_strategy:${query.paymentStrategy.mode}`);
|
|
1338
|
+
}
|
|
1339
|
+
if (paymentSelection?.score !== void 0) {
|
|
1340
|
+
reasons.push(`payment_score:${paymentSelection.score}`);
|
|
1341
|
+
}
|
|
1342
|
+
for (const scoreReason of paymentSelection?.scoreReasons ?? []) {
|
|
1343
|
+
reasons.push(`payment_score_reason:${scoreReason}`);
|
|
1344
|
+
}
|
|
1345
|
+
if (query.providerIds !== void 0) {
|
|
1346
|
+
reasons.push("provider:allowed");
|
|
1347
|
+
}
|
|
1348
|
+
if (query.maxCostUsd !== void 0 && cost !== void 0) {
|
|
1349
|
+
reasons.push(`cost:${cost.amountUsd}<=${query.maxCostUsd}`);
|
|
1350
|
+
}
|
|
1351
|
+
if ((query.requiredReceiptTypes ?? []).length > 0) {
|
|
1352
|
+
reasons.push(`receipts:${query.requiredReceiptTypes?.join(",")}`);
|
|
1353
|
+
}
|
|
1354
|
+
return reasons;
|
|
1355
|
+
}
|
|
1356
|
+
function paymentSelectionCost(selection) {
|
|
1357
|
+
return selection?.cost;
|
|
1358
|
+
}
|
|
1359
|
+
function validateQueryStringArray(value, name) {
|
|
1360
|
+
if (value !== void 0 && (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0))) {
|
|
1361
|
+
throw new ProbeMeshError({
|
|
1362
|
+
code: "invalid_request",
|
|
1363
|
+
message: `Provider catalog query ${name} must be an array of non-empty strings.`
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
function isProbeMeshPricing(value) {
|
|
1368
|
+
return isRecord4(value) && typeof value.unit === "string" && typeof value.amountUsd === "number" && Number.isFinite(value.amountUsd) && value.amountUsd >= 0 && (value.currency === void 0 || typeof value.currency === "string");
|
|
1369
|
+
}
|
|
1370
|
+
function isReceiptType2(value) {
|
|
1371
|
+
return value === "payment_proof" || value === "settlement_confirmation" || value === "provider_delivery" || value === "provider_response_evidence" || value === "authorization_decision";
|
|
1372
|
+
}
|
|
1373
|
+
function cloneSerializable2(value) {
|
|
1374
|
+
if (value === void 0) {
|
|
1375
|
+
return value;
|
|
1376
|
+
}
|
|
1377
|
+
return JSON.parse(JSON.stringify(value));
|
|
1378
|
+
}
|
|
1379
|
+
function validateProviderCatalogEntry(entry, path, errors) {
|
|
1380
|
+
if (!isRecord4(entry)) {
|
|
1381
|
+
errors.push({
|
|
1382
|
+
path,
|
|
1383
|
+
message: "catalog entry must be an object."
|
|
1384
|
+
});
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
validateNonEmptyString4(entry.artifactId, `${path}.artifactId`, errors);
|
|
1388
|
+
validateIsoTimestamp2(entry.generatedAt, `${path}.generatedAt`, errors);
|
|
1389
|
+
if (entry.status !== "ready" && entry.status !== "rejected") {
|
|
1390
|
+
errors.push({
|
|
1391
|
+
path: `${path}.status`,
|
|
1392
|
+
message: 'status must be "ready" or "rejected".'
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
validateEntryProvider(entry.provider, `${path}.provider`, errors);
|
|
1396
|
+
validateEntryAcceptance(entry.acceptance, `${path}.acceptance`, errors);
|
|
1397
|
+
}
|
|
1398
|
+
function validateEntryProvider(provider, path, errors) {
|
|
1399
|
+
if (!isRecord4(provider)) {
|
|
1400
|
+
errors.push({
|
|
1401
|
+
path,
|
|
1402
|
+
message: "provider must be an object."
|
|
1403
|
+
});
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
validateNonEmptyString4(provider.id, `${path}.id`, errors);
|
|
1407
|
+
validateNonEmptyString4(provider.displayName, `${path}.displayName`, errors);
|
|
1408
|
+
validateStringArray3(provider.capabilities, `${path}.capabilities`, errors);
|
|
1409
|
+
validateNonEmptyString4(provider.protocolMode, `${path}.protocolMode`, errors);
|
|
1410
|
+
validateProviderPaymentOptions(
|
|
1411
|
+
provider.paymentOptions,
|
|
1412
|
+
`${path}.paymentOptions`,
|
|
1413
|
+
Array.isArray(provider.capabilities) ? provider.capabilities : [],
|
|
1414
|
+
errors
|
|
1415
|
+
);
|
|
1416
|
+
validatePricingDeclaration3(provider.pricing, `${path}.pricing`, errors);
|
|
1417
|
+
validateReceiptSupport(provider.receipts, `${path}.receipts`, errors);
|
|
1418
|
+
if (provider.limits !== void 0 && !isRecord4(provider.limits)) {
|
|
1419
|
+
errors.push({
|
|
1420
|
+
path: `${path}.limits`,
|
|
1421
|
+
message: "limits must be an object when provided."
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function validateEntryAcceptance(acceptance, path, errors) {
|
|
1426
|
+
if (!isRecord4(acceptance)) {
|
|
1427
|
+
errors.push({
|
|
1428
|
+
path,
|
|
1429
|
+
message: "acceptance must be an object."
|
|
1430
|
+
});
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (!isRecord4(acceptance.gate)) {
|
|
1434
|
+
errors.push({
|
|
1435
|
+
path: `${path}.gate`,
|
|
1436
|
+
message: "gate must be an object."
|
|
1437
|
+
});
|
|
1438
|
+
} else {
|
|
1439
|
+
if (typeof acceptance.gate.accepted !== "boolean") {
|
|
1440
|
+
errors.push({
|
|
1441
|
+
path: `${path}.gate.accepted`,
|
|
1442
|
+
message: "gate.accepted must be a boolean."
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
if (acceptance.gate.status !== "accepted" && acceptance.gate.status !== "rejected") {
|
|
1446
|
+
errors.push({
|
|
1447
|
+
path: `${path}.gate.status`,
|
|
1448
|
+
message: 'gate.status must be "accepted" or "rejected".'
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
validateStringArray3(acceptance.failedChecks, `${path}.failedChecks`, errors);
|
|
1453
|
+
validateReceiptSummary2(acceptance.receipts, `${path}.receipts`, errors);
|
|
1454
|
+
validateStringArray3(
|
|
1455
|
+
acceptance.timelineEventTypes,
|
|
1456
|
+
`${path}.timelineEventTypes`,
|
|
1457
|
+
errors
|
|
1458
|
+
);
|
|
1459
|
+
validateStringArray3(
|
|
1460
|
+
acceptance.telemetryEventTypes,
|
|
1461
|
+
`${path}.telemetryEventTypes`,
|
|
1462
|
+
errors
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
function validatePricingDeclaration3(pricing, path, errors) {
|
|
1466
|
+
if (!isRecord4(pricing)) {
|
|
1467
|
+
errors.push({
|
|
1468
|
+
path,
|
|
1469
|
+
message: "pricing must be an object."
|
|
1470
|
+
});
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
if (isPricingObject(pricing)) {
|
|
1474
|
+
validatePricingObject(pricing, path, errors);
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
for (const [capability, capabilityPricing] of Object.entries(pricing)) {
|
|
1478
|
+
validateNonEmptyString4(capability, `${path}.${capability}`, errors);
|
|
1479
|
+
validatePricingObject(capabilityPricing, `${path}.${capability}`, errors);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
function validatePricingObject(pricing, path, errors) {
|
|
1483
|
+
if (!isRecord4(pricing)) {
|
|
1484
|
+
errors.push({
|
|
1485
|
+
path,
|
|
1486
|
+
message: "pricing entry must be an object."
|
|
1487
|
+
});
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
validateNonEmptyString4(pricing.unit, `${path}.unit`, errors);
|
|
1491
|
+
if (typeof pricing.amountUsd !== "number" || !Number.isFinite(pricing.amountUsd) || pricing.amountUsd < 0) {
|
|
1492
|
+
errors.push({
|
|
1493
|
+
path: `${path}.amountUsd`,
|
|
1494
|
+
message: "amountUsd must be a non-negative finite number."
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
if (pricing.currency !== void 0 && typeof pricing.currency !== "string") {
|
|
1498
|
+
errors.push({
|
|
1499
|
+
path: `${path}.currency`,
|
|
1500
|
+
message: "currency must be a string when provided."
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function validateReceiptSupport(receipts, path, errors) {
|
|
1505
|
+
if (!isRecord4(receipts)) {
|
|
1506
|
+
errors.push({
|
|
1507
|
+
path,
|
|
1508
|
+
message: "receipts must be an object."
|
|
1509
|
+
});
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
for (const key of ["payment", "delivery", "responseEvidence"]) {
|
|
1513
|
+
if (typeof receipts[key] !== "boolean") {
|
|
1514
|
+
errors.push({
|
|
1515
|
+
path: `${path}.${key}`,
|
|
1516
|
+
message: `${key} must be a boolean.`
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
function validateReceiptSummary2(receipts, path, errors) {
|
|
1522
|
+
if (!isRecord4(receipts)) {
|
|
1523
|
+
errors.push({
|
|
1524
|
+
path,
|
|
1525
|
+
message: "receipts must be an object."
|
|
1526
|
+
});
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (typeof receipts.total !== "number" || !Number.isFinite(receipts.total)) {
|
|
1530
|
+
errors.push({
|
|
1531
|
+
path: `${path}.total`,
|
|
1532
|
+
message: "total must be a finite number."
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
validateStringArray3(receipts.types, `${path}.types`, errors);
|
|
1536
|
+
validateStringArray3(receipts.providers, `${path}.providers`, errors);
|
|
1537
|
+
for (const key of [
|
|
1538
|
+
"hasPaymentProof",
|
|
1539
|
+
"hasSettlementConfirmation",
|
|
1540
|
+
"hasProviderDelivery"
|
|
1541
|
+
]) {
|
|
1542
|
+
if (typeof receipts[key] !== "boolean") {
|
|
1543
|
+
errors.push({
|
|
1544
|
+
path: `${path}.${key}`,
|
|
1545
|
+
message: `${key} must be a boolean.`
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function isPricingObject(value) {
|
|
1551
|
+
return "unit" in value || "amountUsd" in value || "currency" in value;
|
|
1552
|
+
}
|
|
1553
|
+
function validateStringArray3(value, path, errors) {
|
|
1554
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
1555
|
+
errors.push({
|
|
1556
|
+
path,
|
|
1557
|
+
message: `${path} must be an array of non-empty strings.`
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function validateNonEmptyString4(value, path, errors) {
|
|
1562
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1563
|
+
errors.push({
|
|
1564
|
+
path,
|
|
1565
|
+
message: `${path} must be a non-empty string.`
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function validateIsoTimestamp2(value, path, errors) {
|
|
1570
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
1571
|
+
errors.push({
|
|
1572
|
+
path,
|
|
1573
|
+
message: `${path} must be an ISO timestamp string.`
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function formatValidationErrors(errors) {
|
|
1578
|
+
return errors.map((error) => `${error.path}: ${error.message}`).join("; ");
|
|
1579
|
+
}
|
|
1580
|
+
function isRecord4(value) {
|
|
1581
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// src/providerCatalogPolicy.ts
|
|
1585
|
+
function evaluateProviderCatalogPolicy(options) {
|
|
1586
|
+
assertEvaluationOptions(options);
|
|
1587
|
+
const catalogPolicy = resolveCatalogPolicy(options.catalogPolicy);
|
|
1588
|
+
const adapterInventory = options.adapterInventory;
|
|
1589
|
+
const adapterIds = new Set(adapterInventory.map((adapter) => adapter.id));
|
|
1590
|
+
const catalogSummary = summarizeProviderCatalog(options.catalog);
|
|
1591
|
+
const checks = [
|
|
1592
|
+
{
|
|
1593
|
+
name: "catalog_valid",
|
|
1594
|
+
status: "passed",
|
|
1595
|
+
message: "Provider catalog is valid."
|
|
1596
|
+
},
|
|
1597
|
+
{
|
|
1598
|
+
name: "adapter_inventory_valid",
|
|
1599
|
+
status: "passed",
|
|
1600
|
+
message: "Adapter inventory is valid."
|
|
1601
|
+
}
|
|
1602
|
+
];
|
|
1603
|
+
const queryResults = options.queries.map(
|
|
1604
|
+
(query) => evaluateQuery({
|
|
1605
|
+
catalog: options.catalog,
|
|
1606
|
+
catalogPolicy,
|
|
1607
|
+
query,
|
|
1608
|
+
adapterInventory,
|
|
1609
|
+
adapterIds,
|
|
1610
|
+
checks
|
|
1611
|
+
})
|
|
1612
|
+
);
|
|
1613
|
+
const failedChecks = checks.filter((check) => check.status === "failed").length;
|
|
1614
|
+
const routeableMatches = queryResults.reduce(
|
|
1615
|
+
(total, result) => total + result.routeableMatches.length,
|
|
1616
|
+
0
|
|
1617
|
+
);
|
|
1618
|
+
const missingAdapterMatches = queryResults.reduce(
|
|
1619
|
+
(total, result) => total + result.missingAdapterMatches.length,
|
|
1620
|
+
0
|
|
1621
|
+
);
|
|
1622
|
+
return {
|
|
1623
|
+
status: failedChecks === 0 ? "passed" : "failed",
|
|
1624
|
+
checks,
|
|
1625
|
+
summary: {
|
|
1626
|
+
...catalogSummary,
|
|
1627
|
+
totalQueries: options.queries.length,
|
|
1628
|
+
passedChecks: checks.length - failedChecks,
|
|
1629
|
+
failedChecks,
|
|
1630
|
+
routeableMatches,
|
|
1631
|
+
missingAdapterMatches
|
|
1632
|
+
},
|
|
1633
|
+
queryResults
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function assertEvaluationOptions(options) {
|
|
1637
|
+
if (!isRecord5(options)) {
|
|
1638
|
+
throw new ProbeMeshError({
|
|
1639
|
+
code: "invalid_request",
|
|
1640
|
+
message: "Provider catalog policy evaluation options must be an object."
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
assertProviderCatalog(options.catalog);
|
|
1644
|
+
assertAdapterInventory(options.adapterInventory);
|
|
1645
|
+
if (!Array.isArray(options.queries) || options.queries.length === 0) {
|
|
1646
|
+
throw new ProbeMeshError({
|
|
1647
|
+
code: "invalid_request",
|
|
1648
|
+
message: "Provider catalog policy evaluation requires at least one query."
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
options.queries.forEach(
|
|
1652
|
+
(query, index) => assertExpectedQuery(query, `queries.${index}`)
|
|
1653
|
+
);
|
|
1654
|
+
assertCatalogPolicyOptions(options.catalogPolicy, "catalogPolicy");
|
|
1655
|
+
}
|
|
1656
|
+
function assertAdapterInventory(adapterInventory) {
|
|
1657
|
+
if (!Array.isArray(adapterInventory)) {
|
|
1658
|
+
throw new ProbeMeshError({
|
|
1659
|
+
code: "invalid_request",
|
|
1660
|
+
message: "Provider catalog policy adapterInventory must be an array."
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1664
|
+
adapterInventory.forEach((adapter, index) => {
|
|
1665
|
+
const path = `adapterInventory.${index}`;
|
|
1666
|
+
if (!isRecord5(adapter)) {
|
|
1667
|
+
throw new ProbeMeshError({
|
|
1668
|
+
code: "invalid_request",
|
|
1669
|
+
message: `${path} must be an object.`
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
if (typeof adapter.id !== "string" || adapter.id.length === 0) {
|
|
1673
|
+
throw new ProbeMeshError({
|
|
1674
|
+
code: "invalid_request",
|
|
1675
|
+
message: `${path}.id must be a non-empty string.`
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
if (seen.has(adapter.id)) {
|
|
1679
|
+
throw new ProbeMeshError({
|
|
1680
|
+
code: "invalid_request",
|
|
1681
|
+
message: `${path}.id duplicates adapter "${adapter.id}".`
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
seen.add(adapter.id);
|
|
1685
|
+
if (!Array.isArray(adapter.capabilities) || adapter.capabilities.some(
|
|
1686
|
+
(capability) => typeof capability !== "string" || capability.length === 0
|
|
1687
|
+
)) {
|
|
1688
|
+
throw new ProbeMeshError({
|
|
1689
|
+
code: "invalid_request",
|
|
1690
|
+
message: `${path}.capabilities must be an array of non-empty strings.`
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
if (adapter.paymentOptions !== void 0 && (!Array.isArray(adapter.paymentOptions) || adapter.paymentOptions.some(
|
|
1694
|
+
(option) => !isRecord5(option) || typeof option.id !== "string" || option.id.length === 0 || typeof option.protocolMode !== "string" || option.protocolMode.length === 0
|
|
1695
|
+
))) {
|
|
1696
|
+
throw new ProbeMeshError({
|
|
1697
|
+
code: "invalid_request",
|
|
1698
|
+
message: `${path}.paymentOptions must be an array of payment option objects when provided.`
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
function assertExpectedQuery(query, path) {
|
|
1704
|
+
if (!isRecord5(query)) {
|
|
1705
|
+
throw new ProbeMeshError({
|
|
1706
|
+
code: "invalid_request",
|
|
1707
|
+
message: `${path} must be an object.`
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
if (typeof query.capability !== "string" || query.capability.length === 0) {
|
|
1711
|
+
throw new ProbeMeshError({
|
|
1712
|
+
code: "invalid_request",
|
|
1713
|
+
message: `${path}.capability must be a non-empty string.`
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
const minMatches = query.minMatches;
|
|
1717
|
+
if (minMatches !== void 0 && (typeof minMatches !== "number" || !Number.isInteger(minMatches) || minMatches < 0)) {
|
|
1718
|
+
throw new ProbeMeshError({
|
|
1719
|
+
code: "invalid_request",
|
|
1720
|
+
message: `${path}.minMatches must be a non-negative integer.`
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
assertCatalogPolicyOptions(query, path);
|
|
1724
|
+
}
|
|
1725
|
+
function assertCatalogPolicyOptions(policy, path) {
|
|
1726
|
+
if (policy === void 0) {
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (!isRecord5(policy)) {
|
|
1730
|
+
throw new ProbeMeshError({
|
|
1731
|
+
code: "invalid_request",
|
|
1732
|
+
message: `${path} must be an object when provided.`
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
if (policy.status !== void 0 && policy.status !== "ready" && policy.status !== "rejected") {
|
|
1736
|
+
throw new ProbeMeshError({
|
|
1737
|
+
code: "invalid_request",
|
|
1738
|
+
message: `${path}.status must be "ready" or "rejected".`
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
if (policy.protocolMode !== void 0 && (typeof policy.protocolMode !== "string" || policy.protocolMode.length === 0)) {
|
|
1742
|
+
throw new ProbeMeshError({
|
|
1743
|
+
code: "invalid_request",
|
|
1744
|
+
message: `${path}.protocolMode must be a non-empty string.`
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
validatePaymentPreferences(policy.paymentPreferences, `${path}.paymentPreferences`);
|
|
1748
|
+
validatePaymentStrategy(policy.paymentStrategy, `${path}.paymentStrategy`);
|
|
1749
|
+
if (policy.maxCostUsd !== void 0 && (typeof policy.maxCostUsd !== "number" || !Number.isFinite(policy.maxCostUsd) || policy.maxCostUsd < 0)) {
|
|
1750
|
+
throw new ProbeMeshError({
|
|
1751
|
+
code: "invalid_request",
|
|
1752
|
+
message: `${path}.maxCostUsd must be a non-negative finite number.`
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
validateStringArray4(policy.providerIds, `${path}.providerIds`);
|
|
1756
|
+
validateStringArray4(policy.blockedProviderIds, `${path}.blockedProviderIds`);
|
|
1757
|
+
if (policy.requiredReceiptTypes !== void 0 && (!Array.isArray(policy.requiredReceiptTypes) || policy.requiredReceiptTypes.some((receiptType) => !isReceiptType3(receiptType)))) {
|
|
1758
|
+
throw new ProbeMeshError({
|
|
1759
|
+
code: "invalid_request",
|
|
1760
|
+
message: `${path}.requiredReceiptTypes must contain known receipt types.`
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function evaluateQuery(options) {
|
|
1765
|
+
const resolvedQuery = resolveQuery(options.catalogPolicy, options.query);
|
|
1766
|
+
const matches = findProviderCatalogMatches(options.catalog, resolvedQuery);
|
|
1767
|
+
const routeableMatches = matches.filter(
|
|
1768
|
+
(match) => hasRouteableAdapter(match, options.adapterInventory)
|
|
1769
|
+
);
|
|
1770
|
+
const missingAdapterMatches = matches.filter((match) => !options.adapterIds.has(match.providerId)).map((match) => ({
|
|
1771
|
+
providerId: match.providerId,
|
|
1772
|
+
capability: match.capability,
|
|
1773
|
+
catalogArtifactId: match.entry.artifactId,
|
|
1774
|
+
catalogStatus: match.entry.status,
|
|
1775
|
+
reasons: [
|
|
1776
|
+
...match.reasons,
|
|
1777
|
+
`missing_adapter:${match.providerId}`
|
|
1778
|
+
]
|
|
1779
|
+
}));
|
|
1780
|
+
const skippedProviders = createSkippedProviders(
|
|
1781
|
+
options.catalog.entries,
|
|
1782
|
+
options.query.capability,
|
|
1783
|
+
matches,
|
|
1784
|
+
resolvedQuery,
|
|
1785
|
+
options.adapterInventory
|
|
1786
|
+
);
|
|
1787
|
+
const minMatches = options.query.minMatches ?? 1;
|
|
1788
|
+
pushCheck(options.checks, {
|
|
1789
|
+
name: "ready_matches",
|
|
1790
|
+
status: matches.length >= minMatches ? "passed" : "failed",
|
|
1791
|
+
capability: options.query.capability,
|
|
1792
|
+
message: matches.length >= minMatches ? `Found ${matches.length} catalog match(es) for "${options.query.capability}".` : `Expected at least ${minMatches} catalog match(es) for "${options.query.capability}", found ${matches.length}.`
|
|
1793
|
+
});
|
|
1794
|
+
pushCheck(options.checks, {
|
|
1795
|
+
name: "routeable_matches",
|
|
1796
|
+
status: routeableMatches.length >= minMatches ? "passed" : "failed",
|
|
1797
|
+
capability: options.query.capability,
|
|
1798
|
+
message: routeableMatches.length >= minMatches ? `Found ${routeableMatches.length} routeable adapter-backed match(es) for "${options.query.capability}".` : `Expected at least ${minMatches} routeable adapter-backed match(es) for "${options.query.capability}", found ${routeableMatches.length}.`
|
|
1799
|
+
});
|
|
1800
|
+
for (const missingMatch of missingAdapterMatches) {
|
|
1801
|
+
pushCheck(options.checks, {
|
|
1802
|
+
name: "missing_adapter",
|
|
1803
|
+
status: routeableMatches.length >= minMatches ? "passed" : "failed",
|
|
1804
|
+
capability: missingMatch.capability,
|
|
1805
|
+
providerId: missingMatch.providerId,
|
|
1806
|
+
message: routeableMatches.length >= minMatches ? `Catalog provider "${missingMatch.providerId}" matches policy but is missing from adapter inventory; minimum routeable matches are still satisfied.` : `Catalog provider "${missingMatch.providerId}" matches policy but is missing from adapter inventory.`
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
return {
|
|
1810
|
+
query: options.query,
|
|
1811
|
+
matches,
|
|
1812
|
+
routeableMatches,
|
|
1813
|
+
missingAdapterMatches,
|
|
1814
|
+
skippedProviders
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
function resolveCatalogPolicy(policy) {
|
|
1818
|
+
return {
|
|
1819
|
+
enabled: policy?.enabled ?? true,
|
|
1820
|
+
status: policy?.status ?? "ready",
|
|
1821
|
+
protocolMode: policy?.protocolMode,
|
|
1822
|
+
paymentPreferences: policy?.paymentPreferences,
|
|
1823
|
+
paymentStrategy: policy?.paymentStrategy,
|
|
1824
|
+
maxCostUsd: policy?.maxCostUsd,
|
|
1825
|
+
requiredReceiptTypes: policy?.requiredReceiptTypes,
|
|
1826
|
+
providerIds: policy?.providerIds,
|
|
1827
|
+
blockedProviderIds: policy?.blockedProviderIds
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
function resolveQuery(policy, query) {
|
|
1831
|
+
const queryPolicy = resolveCatalogPolicy(query);
|
|
1832
|
+
const activePolicy = policy.enabled ? policy : resolveCatalogPolicy(void 0);
|
|
1833
|
+
return {
|
|
1834
|
+
capability: query.capability,
|
|
1835
|
+
protocolMode: queryPolicy.protocolMode ?? activePolicy.protocolMode,
|
|
1836
|
+
paymentPreferences: mergePaymentPreferences(
|
|
1837
|
+
activePolicy.paymentPreferences,
|
|
1838
|
+
queryPolicy.paymentPreferences
|
|
1839
|
+
),
|
|
1840
|
+
paymentStrategy: queryPolicy.paymentStrategy ?? activePolicy.paymentStrategy,
|
|
1841
|
+
maxCostUsd: strictestMaxCostUsd(
|
|
1842
|
+
queryPolicy.maxCostUsd,
|
|
1843
|
+
activePolicy.maxCostUsd
|
|
1844
|
+
),
|
|
1845
|
+
requiredReceiptTypes: unionProviderIds(
|
|
1846
|
+
queryPolicy.requiredReceiptTypes,
|
|
1847
|
+
activePolicy.requiredReceiptTypes
|
|
1848
|
+
),
|
|
1849
|
+
providerIds: intersectProviderIds(
|
|
1850
|
+
queryPolicy.providerIds,
|
|
1851
|
+
activePolicy.providerIds
|
|
1852
|
+
),
|
|
1853
|
+
blockedProviderIds: unionProviderIds(
|
|
1854
|
+
queryPolicy.blockedProviderIds,
|
|
1855
|
+
activePolicy.blockedProviderIds
|
|
1856
|
+
),
|
|
1857
|
+
status: queryPolicy.status ?? activePolicy.status
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
function hasRouteableAdapter(match, adapterInventory) {
|
|
1861
|
+
return adapterInventory.some(
|
|
1862
|
+
(adapter) => adapter.id === match.providerId && adapter.capabilities.includes(match.capability) && (adapter.paymentOptions === void 0 || match.paymentSelection === void 0 || adapter.paymentOptions.some(
|
|
1863
|
+
(option) => option.id === match.paymentSelection?.optionId
|
|
1864
|
+
))
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
function createSkippedProviders(entries, capability, matches, query, adapterInventory) {
|
|
1868
|
+
const matchIds = new Set(matches.map((match) => match.providerId));
|
|
1869
|
+
const adapterIds = new Set(adapterInventory.map((adapter) => adapter.id));
|
|
1870
|
+
const catalogProviderIds = new Set(entries.map((entry) => entry.provider.id));
|
|
1871
|
+
const skippedCatalogEntries = entries.filter((entry) => entry.provider.capabilities.includes(capability)).filter((entry) => !matchIds.has(entry.provider.id)).map((entry) => ({
|
|
1872
|
+
providerId: entry.provider.id,
|
|
1873
|
+
capability,
|
|
1874
|
+
catalogArtifactId: entry.artifactId,
|
|
1875
|
+
catalogStatus: entry.status,
|
|
1876
|
+
reasons: explainSkippedEntry(entry, query, adapterIds)
|
|
1877
|
+
}));
|
|
1878
|
+
const inventoryOnlyAdapters = adapterInventory.filter((adapter) => adapter.capabilities.includes(capability)).filter((adapter) => !catalogProviderIds.has(adapter.id)).map((adapter) => ({
|
|
1879
|
+
providerId: adapter.id,
|
|
1880
|
+
capability,
|
|
1881
|
+
inventoryOnly: true,
|
|
1882
|
+
reasons: ["adapter:not_in_catalog"]
|
|
1883
|
+
}));
|
|
1884
|
+
return [...skippedCatalogEntries, ...inventoryOnlyAdapters];
|
|
1885
|
+
}
|
|
1886
|
+
function explainSkippedEntry(entry, query, adapterIds) {
|
|
1887
|
+
const reasons = [];
|
|
1888
|
+
const providerId = entry.provider.id;
|
|
1889
|
+
if (entry.status !== (query.status ?? "ready")) {
|
|
1890
|
+
reasons.push(`status:${entry.status}`);
|
|
1891
|
+
}
|
|
1892
|
+
if (query.protocolMode && entry.provider.protocolMode !== query.protocolMode) {
|
|
1893
|
+
reasons.push(`protocol:${entry.provider.protocolMode}`);
|
|
1894
|
+
}
|
|
1895
|
+
if (query.paymentPreferences) {
|
|
1896
|
+
const paymentMatch = findProviderCatalogMatches(
|
|
1897
|
+
{
|
|
1898
|
+
schemaVersion: "probemesh.provider-catalog.index.v1",
|
|
1899
|
+
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
1900
|
+
entries: [entry]
|
|
1901
|
+
},
|
|
1902
|
+
{
|
|
1903
|
+
capability: query.capability,
|
|
1904
|
+
paymentPreferences: query.paymentPreferences,
|
|
1905
|
+
status: entry.status
|
|
1906
|
+
}
|
|
1907
|
+
);
|
|
1908
|
+
if (paymentMatch.length === 0) {
|
|
1909
|
+
reasons.push("payment_preferences:not_matched");
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (query.providerIds && !query.providerIds.includes(providerId)) {
|
|
1913
|
+
reasons.push("provider:not_allowed");
|
|
1914
|
+
}
|
|
1915
|
+
if (query.blockedProviderIds?.includes(providerId)) {
|
|
1916
|
+
reasons.push("provider:blocked");
|
|
1917
|
+
}
|
|
1918
|
+
const cost = resolveCatalogCost2(entry, query.capability);
|
|
1919
|
+
if (query.maxCostUsd !== void 0 && (cost === void 0 || cost.amountUsd > query.maxCostUsd)) {
|
|
1920
|
+
reasons.push(`cost:${cost?.amountUsd ?? "unknown"}>${query.maxCostUsd}`);
|
|
1921
|
+
}
|
|
1922
|
+
const receiptTypes = new Set(entry.acceptance.receipts.types);
|
|
1923
|
+
for (const receiptType of query.requiredReceiptTypes ?? []) {
|
|
1924
|
+
if (!receiptTypes.has(receiptType)) {
|
|
1925
|
+
reasons.push(`missing_receipt:${receiptType}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
if (!adapterIds.has(providerId)) {
|
|
1929
|
+
reasons.push(`missing_adapter:${providerId}`);
|
|
1930
|
+
}
|
|
1931
|
+
return reasons.length > 0 ? reasons : ["not_matched"];
|
|
1932
|
+
}
|
|
1933
|
+
function resolveCatalogCost2(entry, capability) {
|
|
1934
|
+
const pricing = entry.provider.pricing;
|
|
1935
|
+
if (isPricing2(pricing)) {
|
|
1936
|
+
return pricing;
|
|
1937
|
+
}
|
|
1938
|
+
const capabilityPricing = pricing[capability];
|
|
1939
|
+
return isPricing2(capabilityPricing) ? capabilityPricing : void 0;
|
|
1940
|
+
}
|
|
1941
|
+
function pushCheck(checks, check) {
|
|
1942
|
+
checks.push(check);
|
|
1943
|
+
}
|
|
1944
|
+
function strictestMaxCostUsd(first, second) {
|
|
1945
|
+
if (first === void 0) {
|
|
1946
|
+
return second;
|
|
1947
|
+
}
|
|
1948
|
+
if (second === void 0) {
|
|
1949
|
+
return first;
|
|
1950
|
+
}
|
|
1951
|
+
return Math.min(first, second);
|
|
1952
|
+
}
|
|
1953
|
+
function intersectProviderIds(first, second) {
|
|
1954
|
+
if (!first) {
|
|
1955
|
+
return second;
|
|
1956
|
+
}
|
|
1957
|
+
if (!second) {
|
|
1958
|
+
return first;
|
|
1959
|
+
}
|
|
1960
|
+
return first.filter((providerId) => second.includes(providerId));
|
|
1961
|
+
}
|
|
1962
|
+
function unionProviderIds(first, second) {
|
|
1963
|
+
return [.../* @__PURE__ */ new Set([...first ?? [], ...second ?? []])];
|
|
1964
|
+
}
|
|
1965
|
+
function validateStringArray4(value, path) {
|
|
1966
|
+
if (value !== void 0 && (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0))) {
|
|
1967
|
+
throw new ProbeMeshError({
|
|
1968
|
+
code: "invalid_request",
|
|
1969
|
+
message: `${path} must be an array of non-empty strings.`
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
function isPricing2(value) {
|
|
1974
|
+
return isRecord5(value) && typeof value.unit === "string" && typeof value.amountUsd === "number" && Number.isFinite(value.amountUsd) && value.amountUsd >= 0 && (value.currency === void 0 || typeof value.currency === "string");
|
|
1975
|
+
}
|
|
1976
|
+
function isReceiptType3(value) {
|
|
1977
|
+
return value === "payment_proof" || value === "settlement_confirmation" || value === "provider_delivery" || value === "provider_response_evidence" || value === "authorization_decision";
|
|
1978
|
+
}
|
|
1979
|
+
function isRecord5(value) {
|
|
1980
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// src/providerOnboardingDossier.ts
|
|
1984
|
+
var import_node_crypto2 = require("crypto");
|
|
1985
|
+
|
|
1986
|
+
// src/providerConformanceArtifact.ts
|
|
1987
|
+
var import_node_crypto = require("crypto");
|
|
1988
|
+
var PROVIDER_CONFORMANCE_ARTIFACT_SCHEMA_VERSION = "probemesh.provider-conformance.v1";
|
|
1989
|
+
function validateProviderConformanceArtifact(artifact) {
|
|
1990
|
+
const errors = [];
|
|
1991
|
+
if (!isRecord6(artifact)) {
|
|
1992
|
+
return {
|
|
1993
|
+
valid: false,
|
|
1994
|
+
errors: [
|
|
1995
|
+
{
|
|
1996
|
+
path: "$",
|
|
1997
|
+
message: "Provider conformance artifact must be an object."
|
|
1998
|
+
}
|
|
1999
|
+
]
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
if (artifact.schemaVersion !== PROVIDER_CONFORMANCE_ARTIFACT_SCHEMA_VERSION) {
|
|
2003
|
+
errors.push({
|
|
2004
|
+
path: "schemaVersion",
|
|
2005
|
+
message: `schemaVersion must be "${PROVIDER_CONFORMANCE_ARTIFACT_SCHEMA_VERSION}".`
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
validateNonEmptyString5(artifact.artifactId, "artifactId", errors);
|
|
2009
|
+
validateIsoTimestamp3(artifact.generatedAt, "generatedAt", errors);
|
|
2010
|
+
if (artifact.status !== "verified" && artifact.status !== "failed") {
|
|
2011
|
+
errors.push({
|
|
2012
|
+
path: "status",
|
|
2013
|
+
message: 'status must be "verified" or "failed".'
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
if (artifact.hashAlgorithm !== "sha256") {
|
|
2017
|
+
errors.push({
|
|
2018
|
+
path: "hashAlgorithm",
|
|
2019
|
+
message: 'hashAlgorithm must be "sha256".'
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
validateHashes(artifact.hashes, errors);
|
|
2023
|
+
validateManifestSummary(artifact.provider, errors);
|
|
2024
|
+
validateConformanceSummary(artifact.conformance, errors);
|
|
2025
|
+
return {
|
|
2026
|
+
valid: errors.length === 0,
|
|
2027
|
+
errors
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
function validateManifestSummary(provider, errors) {
|
|
2031
|
+
if (!isRecord6(provider)) {
|
|
2032
|
+
errors.push({
|
|
2033
|
+
path: "provider",
|
|
2034
|
+
message: "provider must be an object."
|
|
2035
|
+
});
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
validateNonEmptyString5(provider.id, "provider.id", errors);
|
|
2039
|
+
validateNonEmptyString5(provider.displayName, "provider.displayName", errors);
|
|
2040
|
+
if (!Array.isArray(provider.capabilities) || provider.capabilities.some((capability) => typeof capability !== "string")) {
|
|
2041
|
+
errors.push({
|
|
2042
|
+
path: "provider.capabilities",
|
|
2043
|
+
message: "provider.capabilities must be an array of strings."
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
validateNonEmptyString5(provider.protocolMode, "provider.protocolMode", errors);
|
|
2047
|
+
if (!isRecord6(provider.receipts)) {
|
|
2048
|
+
errors.push({
|
|
2049
|
+
path: "provider.receipts",
|
|
2050
|
+
message: "provider.receipts must be an object."
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
if (!isRecord6(provider.schemas) || typeof provider.schemas.inputDeclared !== "boolean" || typeof provider.schemas.outputDeclared !== "boolean") {
|
|
2054
|
+
errors.push({
|
|
2055
|
+
path: "provider.schemas",
|
|
2056
|
+
message: "provider.schemas must include inputDeclared and outputDeclared booleans."
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
function validateConformanceSummary(conformance, errors) {
|
|
2061
|
+
if (!isRecord6(conformance)) {
|
|
2062
|
+
errors.push({
|
|
2063
|
+
path: "conformance",
|
|
2064
|
+
message: "conformance must be an object."
|
|
2065
|
+
});
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
validateNonEmptyString5(conformance.title, "conformance.title", errors);
|
|
2069
|
+
if (conformance.status !== "passed" && conformance.status !== "failed") {
|
|
2070
|
+
errors.push({
|
|
2071
|
+
path: "conformance.status",
|
|
2072
|
+
message: 'conformance.status must be "passed" or "failed".'
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
if (!isRecord6(conformance.summary)) {
|
|
2076
|
+
errors.push({
|
|
2077
|
+
path: "conformance.summary",
|
|
2078
|
+
message: "conformance.summary must be an object."
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
if (!Array.isArray(conformance.checks) || conformance.checks.some((check) => !isRecord6(check))) {
|
|
2082
|
+
errors.push({
|
|
2083
|
+
path: "conformance.checks",
|
|
2084
|
+
message: "conformance.checks must be an array of check objects."
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
if (!Array.isArray(conformance.failedChecks) || conformance.failedChecks.some((checkName) => typeof checkName !== "string")) {
|
|
2088
|
+
errors.push({
|
|
2089
|
+
path: "conformance.failedChecks",
|
|
2090
|
+
message: "conformance.failedChecks must be an array of strings."
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
if (!isRecord6(conformance.receipts)) {
|
|
2094
|
+
errors.push({
|
|
2095
|
+
path: "conformance.receipts",
|
|
2096
|
+
message: "conformance.receipts must be an object."
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
if (!Array.isArray(conformance.timelineEventTypes) || conformance.timelineEventTypes.some((eventType) => typeof eventType !== "string")) {
|
|
2100
|
+
errors.push({
|
|
2101
|
+
path: "conformance.timelineEventTypes",
|
|
2102
|
+
message: "conformance.timelineEventTypes must be an array of strings."
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
if (!Array.isArray(conformance.telemetryEventTypes) || conformance.telemetryEventTypes.some((eventType) => typeof eventType !== "string")) {
|
|
2106
|
+
errors.push({
|
|
2107
|
+
path: "conformance.telemetryEventTypes",
|
|
2108
|
+
message: "conformance.telemetryEventTypes must be an array of strings."
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
function validateHashes(hashes, errors) {
|
|
2113
|
+
if (!isRecord6(hashes)) {
|
|
2114
|
+
errors.push({
|
|
2115
|
+
path: "hashes",
|
|
2116
|
+
message: "hashes must be an object."
|
|
2117
|
+
});
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
for (const field of ["manifest", "report", "checks", "artifact"]) {
|
|
2121
|
+
const value = hashes[field];
|
|
2122
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) {
|
|
2123
|
+
errors.push({
|
|
2124
|
+
path: `hashes.${field}`,
|
|
2125
|
+
message: `${field} hash must be a sha256 hex string.`
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
function validateNonEmptyString5(value, path, errors) {
|
|
2131
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2132
|
+
errors.push({
|
|
2133
|
+
path,
|
|
2134
|
+
message: `${path} must be a non-empty string.`
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
function validateIsoTimestamp3(value, path, errors) {
|
|
2139
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
2140
|
+
errors.push({
|
|
2141
|
+
path,
|
|
2142
|
+
message: `${path} must be an ISO timestamp.`
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
function isRecord6(value) {
|
|
2147
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// src/providerOnboardingDossier.ts
|
|
2151
|
+
var PROVIDER_ONBOARDING_DOSSIER_SCHEMA_VERSION = "probemesh.provider-onboarding-dossier.v1";
|
|
2152
|
+
function validateProviderOnboardingDossier(dossier) {
|
|
2153
|
+
const errors = [];
|
|
2154
|
+
if (!isRecord7(dossier)) {
|
|
2155
|
+
return {
|
|
2156
|
+
valid: false,
|
|
2157
|
+
errors: [
|
|
2158
|
+
{
|
|
2159
|
+
path: "$",
|
|
2160
|
+
message: "Provider onboarding dossier must be an object."
|
|
2161
|
+
}
|
|
2162
|
+
]
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
if (dossier.schemaVersion !== PROVIDER_ONBOARDING_DOSSIER_SCHEMA_VERSION) {
|
|
2166
|
+
errors.push({
|
|
2167
|
+
path: "schemaVersion",
|
|
2168
|
+
message: `schemaVersion must be "${PROVIDER_ONBOARDING_DOSSIER_SCHEMA_VERSION}".`
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
validateNonEmptyString6(dossier.dossierId, "dossierId", errors);
|
|
2172
|
+
validateIsoTimestamp4(dossier.generatedAt, "generatedAt", errors);
|
|
2173
|
+
if (dossier.status !== "ready" && dossier.status !== "failed") {
|
|
2174
|
+
errors.push({
|
|
2175
|
+
path: "status",
|
|
2176
|
+
message: 'status must be "ready" or "failed".'
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
if (dossier.hashAlgorithm !== "sha256") {
|
|
2180
|
+
errors.push({
|
|
2181
|
+
path: "hashAlgorithm",
|
|
2182
|
+
message: 'hashAlgorithm must be "sha256".'
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
validateProviderSummary(dossier.provider, errors);
|
|
2186
|
+
validateReadiness(dossier.readiness, errors);
|
|
2187
|
+
validateEvidence(dossier.evidence, errors);
|
|
2188
|
+
validateNestedArtifacts(dossier.artifacts, errors);
|
|
2189
|
+
validateHashes2(dossier, errors);
|
|
2190
|
+
return {
|
|
2191
|
+
valid: errors.length === 0,
|
|
2192
|
+
errors
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
function assertProviderOnboardingDossier(dossier) {
|
|
2196
|
+
const result = validateProviderOnboardingDossier(dossier);
|
|
2197
|
+
if (result.valid) {
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
throw new ProbeMeshError({
|
|
2201
|
+
code: "invalid_request",
|
|
2202
|
+
message: `Invalid provider onboarding dossier: ${formatValidationErrors2(
|
|
2203
|
+
result.errors
|
|
2204
|
+
)}`
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
function validateProviderSummary(provider, errors) {
|
|
2208
|
+
if (!isRecord7(provider)) {
|
|
2209
|
+
errors.push({
|
|
2210
|
+
path: "provider",
|
|
2211
|
+
message: "provider must be an object."
|
|
2212
|
+
});
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
validateNonEmptyString6(provider.id, "provider.id", errors);
|
|
2216
|
+
validateNonEmptyString6(provider.displayName, "provider.displayName", errors);
|
|
2217
|
+
validateNonEmptyString6(provider.protocolMode, "provider.protocolMode", errors);
|
|
2218
|
+
if (!Array.isArray(provider.capabilities) || provider.capabilities.some((capability) => typeof capability !== "string")) {
|
|
2219
|
+
errors.push({
|
|
2220
|
+
path: "provider.capabilities",
|
|
2221
|
+
message: "provider.capabilities must be an array of strings."
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function validateReadiness(readiness, errors) {
|
|
2226
|
+
if (!isRecord7(readiness)) {
|
|
2227
|
+
errors.push({
|
|
2228
|
+
path: "readiness",
|
|
2229
|
+
message: "readiness must be an object."
|
|
2230
|
+
});
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (typeof readiness.ready !== "boolean") {
|
|
2234
|
+
errors.push({
|
|
2235
|
+
path: "readiness.ready",
|
|
2236
|
+
message: "readiness.ready must be a boolean."
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
if (readiness.status !== "ready" && readiness.status !== "failed") {
|
|
2240
|
+
errors.push({
|
|
2241
|
+
path: "readiness.status",
|
|
2242
|
+
message: 'readiness.status must be "ready" or "failed".'
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
if (!Array.isArray(readiness.reasons) || readiness.reasons.some((reason) => typeof reason !== "string")) {
|
|
2246
|
+
errors.push({
|
|
2247
|
+
path: "readiness.reasons",
|
|
2248
|
+
message: "readiness.reasons must be an array of strings."
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
if (!Array.isArray(readiness.failedChecks) || readiness.failedChecks.some((check) => typeof check !== "string")) {
|
|
2252
|
+
errors.push({
|
|
2253
|
+
path: "readiness.failedChecks",
|
|
2254
|
+
message: "readiness.failedChecks must be an array of strings."
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function validateEvidence(evidence, errors) {
|
|
2259
|
+
if (!isRecord7(evidence)) {
|
|
2260
|
+
errors.push({
|
|
2261
|
+
path: "evidence",
|
|
2262
|
+
message: "evidence must be an object."
|
|
2263
|
+
});
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (!isRecord7(evidence.catalog)) {
|
|
2267
|
+
errors.push({
|
|
2268
|
+
path: "evidence.catalog",
|
|
2269
|
+
message: "evidence.catalog must be an object."
|
|
2270
|
+
});
|
|
2271
|
+
} else {
|
|
2272
|
+
validateNonEmptyString6(
|
|
2273
|
+
evidence.catalog.artifactId,
|
|
2274
|
+
"evidence.catalog.artifactId",
|
|
2275
|
+
errors
|
|
2276
|
+
);
|
|
2277
|
+
if (evidence.catalog.status !== "ready" && evidence.catalog.status !== "rejected") {
|
|
2278
|
+
errors.push({
|
|
2279
|
+
path: "evidence.catalog.status",
|
|
2280
|
+
message: 'evidence.catalog.status must be "ready" or "rejected".'
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
if (typeof evidence.catalog.accepted !== "boolean") {
|
|
2284
|
+
errors.push({
|
|
2285
|
+
path: "evidence.catalog.accepted",
|
|
2286
|
+
message: "evidence.catalog.accepted must be a boolean."
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
if (!isRecord7(evidence.conformance)) {
|
|
2291
|
+
errors.push({
|
|
2292
|
+
path: "evidence.conformance",
|
|
2293
|
+
message: "evidence.conformance must be an object."
|
|
2294
|
+
});
|
|
2295
|
+
} else {
|
|
2296
|
+
validateNonEmptyString6(
|
|
2297
|
+
evidence.conformance.artifactId,
|
|
2298
|
+
"evidence.conformance.artifactId",
|
|
2299
|
+
errors
|
|
2300
|
+
);
|
|
2301
|
+
if (evidence.conformance.status !== "verified" && evidence.conformance.status !== "failed") {
|
|
2302
|
+
errors.push({
|
|
2303
|
+
path: "evidence.conformance.status",
|
|
2304
|
+
message: 'evidence.conformance.status must be "verified" or "failed".'
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
if (!isRecord7(evidence.receipts)) {
|
|
2309
|
+
errors.push({
|
|
2310
|
+
path: "evidence.receipts",
|
|
2311
|
+
message: "evidence.receipts must be an object."
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
if (!Array.isArray(evidence.timelineEventTypes) || evidence.timelineEventTypes.some((type) => typeof type !== "string")) {
|
|
2315
|
+
errors.push({
|
|
2316
|
+
path: "evidence.timelineEventTypes",
|
|
2317
|
+
message: "evidence.timelineEventTypes must be an array of strings."
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
if (!Array.isArray(evidence.telemetryEventTypes) || evidence.telemetryEventTypes.some((type) => typeof type !== "string")) {
|
|
2321
|
+
errors.push({
|
|
2322
|
+
path: "evidence.telemetryEventTypes",
|
|
2323
|
+
message: "evidence.telemetryEventTypes must be an array of strings."
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
function validateNestedArtifacts(artifacts, errors) {
|
|
2328
|
+
if (!isRecord7(artifacts)) {
|
|
2329
|
+
errors.push({
|
|
2330
|
+
path: "artifacts",
|
|
2331
|
+
message: "artifacts must be an object."
|
|
2332
|
+
});
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
const catalogValidation = validateProviderCatalogArtifact(artifacts.catalog);
|
|
2336
|
+
for (const error of catalogValidation.errors) {
|
|
2337
|
+
errors.push({
|
|
2338
|
+
path: `artifacts.catalog.${error.path}`,
|
|
2339
|
+
message: error.message
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
const conformanceValidation = validateProviderConformanceArtifact(
|
|
2343
|
+
artifacts.conformance
|
|
2344
|
+
);
|
|
2345
|
+
for (const error of conformanceValidation.errors) {
|
|
2346
|
+
errors.push({
|
|
2347
|
+
path: `artifacts.conformance.${error.path}`,
|
|
2348
|
+
message: error.message
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
if (isRecord7(artifacts.catalog) && isRecord7(artifacts.conformance) && isRecord7(artifacts.catalog.provider) && isRecord7(artifacts.conformance.provider) && artifacts.catalog.provider.id !== artifacts.conformance.provider.id) {
|
|
2352
|
+
errors.push({
|
|
2353
|
+
path: "artifacts",
|
|
2354
|
+
message: "catalog and conformance artifacts must reference the same provider id."
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
function validateHashes2(dossier, errors) {
|
|
2359
|
+
if (!isRecord7(dossier.hashes)) {
|
|
2360
|
+
errors.push({
|
|
2361
|
+
path: "hashes",
|
|
2362
|
+
message: "hashes must be an object."
|
|
2363
|
+
});
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
for (const field of [
|
|
2367
|
+
"catalogArtifact",
|
|
2368
|
+
"conformanceArtifact",
|
|
2369
|
+
"readiness",
|
|
2370
|
+
"dossier"
|
|
2371
|
+
]) {
|
|
2372
|
+
const value = dossier.hashes[field];
|
|
2373
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) {
|
|
2374
|
+
errors.push({
|
|
2375
|
+
path: `hashes.${field}`,
|
|
2376
|
+
message: `${field} hash must be a sha256 hex string.`
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
if (!isRecord7(dossier.artifacts) || !isRecord7(dossier.readiness) || errors.some((error) => error.path.startsWith("hashes."))) {
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
const expectedHashes = {
|
|
2384
|
+
catalogArtifact: hashStableJson(dossier.artifacts.catalog),
|
|
2385
|
+
conformanceArtifact: hashStableJson(dossier.artifacts.conformance),
|
|
2386
|
+
readiness: hashStableJson(dossier.readiness),
|
|
2387
|
+
dossier: ""
|
|
2388
|
+
};
|
|
2389
|
+
const expectedDossierHash = hashDossierContent({
|
|
2390
|
+
...dossier,
|
|
2391
|
+
hashes: expectedHashes
|
|
2392
|
+
});
|
|
2393
|
+
for (const [field, expected] of Object.entries({
|
|
2394
|
+
...expectedHashes,
|
|
2395
|
+
dossier: expectedDossierHash
|
|
2396
|
+
})) {
|
|
2397
|
+
if (dossier.hashes[field] !== expected) {
|
|
2398
|
+
errors.push({
|
|
2399
|
+
path: `hashes.${field}`,
|
|
2400
|
+
message: `${field} hash does not match dossier content.`
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
function hashStableJson(value) {
|
|
2406
|
+
return (0, import_node_crypto2.createHash)("sha256").update(stableStringify(value)).digest("hex");
|
|
2407
|
+
}
|
|
2408
|
+
function hashDossierContent(dossier) {
|
|
2409
|
+
return hashStableJson(dossier);
|
|
2410
|
+
}
|
|
2411
|
+
function stableStringify(value) {
|
|
2412
|
+
if (value === null || typeof value !== "object") {
|
|
2413
|
+
return JSON.stringify(value);
|
|
2414
|
+
}
|
|
2415
|
+
if (Array.isArray(value)) {
|
|
2416
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
2417
|
+
}
|
|
2418
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right));
|
|
2419
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`;
|
|
2420
|
+
}
|
|
2421
|
+
function isRecord7(value) {
|
|
2422
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2423
|
+
}
|
|
2424
|
+
function validateNonEmptyString6(value, path, errors) {
|
|
2425
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2426
|
+
errors.push({
|
|
2427
|
+
path,
|
|
2428
|
+
message: `${path} must be a non-empty string.`
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
function validateIsoTimestamp4(value, path, errors) {
|
|
2433
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
2434
|
+
errors.push({
|
|
2435
|
+
path,
|
|
2436
|
+
message: `${path} must be an ISO timestamp.`
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
function formatValidationErrors2(errors) {
|
|
2441
|
+
return errors.map((error) => `${error.path}: ${error.message}`).join("; ");
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// src/providerOnboardingDossierImport.ts
|
|
2445
|
+
function parseProviderOnboardingDossierJson(json) {
|
|
2446
|
+
if (typeof json !== "string") {
|
|
2447
|
+
throw new ProbeMeshError({
|
|
2448
|
+
code: "invalid_request",
|
|
2449
|
+
message: "Provider onboarding dossier JSON must be a string."
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
let parsed;
|
|
2453
|
+
try {
|
|
2454
|
+
parsed = JSON.parse(json);
|
|
2455
|
+
} catch (cause) {
|
|
2456
|
+
throw new ProbeMeshError({
|
|
2457
|
+
code: "invalid_request",
|
|
2458
|
+
message: "Provider onboarding dossier JSON must be valid JSON.",
|
|
2459
|
+
cause
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
assertProviderOnboardingDossier(parsed);
|
|
2463
|
+
return parsed;
|
|
2464
|
+
}
|
|
2465
|
+
function createProviderCatalogFromOnboardingDossiers(options) {
|
|
2466
|
+
assertDossierListOptions(options);
|
|
2467
|
+
const readyArtifacts = options.dossiers.filter((dossier) => dossier.status === "ready").map((dossier) => dossier.artifacts.catalog);
|
|
2468
|
+
return createProviderCatalog({
|
|
2469
|
+
artifacts: readyArtifacts,
|
|
2470
|
+
generatedAt: options.generatedAt
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
function evaluateProviderOnboardingDossiers(options) {
|
|
2474
|
+
assertEvaluationOptions2(options);
|
|
2475
|
+
const catalog = createProviderCatalogFromOnboardingDossiers({
|
|
2476
|
+
dossiers: options.dossiers,
|
|
2477
|
+
generatedAt: options.generatedAt
|
|
2478
|
+
});
|
|
2479
|
+
const dossierResults = options.dossiers.map(summarizeDossierImport);
|
|
2480
|
+
const readyDossiers = dossierResults.filter((dossier) => dossier.imported).length;
|
|
2481
|
+
const failedDossiers = dossierResults.length - readyDossiers;
|
|
2482
|
+
const checks = [
|
|
2483
|
+
{
|
|
2484
|
+
name: "dossiers_valid",
|
|
2485
|
+
status: "passed",
|
|
2486
|
+
message: `${options.dossiers.length} provider onboarding dossier(s) are valid.`
|
|
2487
|
+
},
|
|
2488
|
+
readyDossiers > 0 ? {
|
|
2489
|
+
name: "ready_dossiers",
|
|
2490
|
+
status: "passed",
|
|
2491
|
+
message: `${readyDossiers} ready dossier(s) were imported into the buyer catalog.`
|
|
2492
|
+
} : {
|
|
2493
|
+
name: "ready_dossiers",
|
|
2494
|
+
status: "failed",
|
|
2495
|
+
message: "No ready dossiers were available for buyer catalog import."
|
|
2496
|
+
}
|
|
2497
|
+
];
|
|
2498
|
+
let policyEvaluation;
|
|
2499
|
+
if (options.policy) {
|
|
2500
|
+
policyEvaluation = evaluateProviderCatalogPolicy({
|
|
2501
|
+
catalog,
|
|
2502
|
+
catalogPolicy: options.policy.catalogPolicy,
|
|
2503
|
+
adapterInventory: options.policy.adapterInventory,
|
|
2504
|
+
queries: options.policy.queries
|
|
2505
|
+
});
|
|
2506
|
+
checks.push({
|
|
2507
|
+
name: "catalog_policy",
|
|
2508
|
+
status: policyEvaluation.status === "passed" ? "passed" : "failed",
|
|
2509
|
+
message: policyEvaluation.status === "passed" ? "Imported dossier catalog passed the buyer catalog policy checks." : "Imported dossier catalog failed the buyer catalog policy checks."
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
if (failedDossiers > 0) {
|
|
2513
|
+
checks.push({
|
|
2514
|
+
name: "failed_dossiers_excluded",
|
|
2515
|
+
status: "passed",
|
|
2516
|
+
message: `${failedDossiers} failed dossier(s) were kept in the report and excluded from the buyer catalog.`
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
const failedChecks = checks.filter((check) => check.status === "failed").length;
|
|
2520
|
+
const catalogSummary = summarizeProviderCatalog(catalog);
|
|
2521
|
+
return {
|
|
2522
|
+
status: failedChecks === 0 ? "passed" : "failed",
|
|
2523
|
+
checks,
|
|
2524
|
+
summary: {
|
|
2525
|
+
...catalogSummary,
|
|
2526
|
+
totalDossiers: options.dossiers.length,
|
|
2527
|
+
readyDossiers,
|
|
2528
|
+
failedDossiers,
|
|
2529
|
+
importedDossiers: readyDossiers,
|
|
2530
|
+
excludedDossiers: failedDossiers,
|
|
2531
|
+
policyStatus: policyEvaluation?.status
|
|
2532
|
+
},
|
|
2533
|
+
catalog,
|
|
2534
|
+
dossiers: dossierResults,
|
|
2535
|
+
policyEvaluation
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
function createProviderOnboardingDossierImportReport(result, options = {}) {
|
|
2539
|
+
const report = {
|
|
2540
|
+
title: options.title ?? "ProbeMesh Provider Onboarding Dossier Import Report",
|
|
2541
|
+
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2542
|
+
status: result.status,
|
|
2543
|
+
summary: cloneSerializable3(result.summary),
|
|
2544
|
+
checks: cloneSerializable3(result.checks),
|
|
2545
|
+
dossiers: cloneSerializable3(result.dossiers),
|
|
2546
|
+
policyEvaluation: cloneSerializable3(result.policyEvaluation)
|
|
2547
|
+
};
|
|
2548
|
+
return sanitizeReport(report, options.forbiddenOutputValues ?? []);
|
|
2549
|
+
}
|
|
2550
|
+
function formatProviderOnboardingDossierImportReport(report, options = {}) {
|
|
2551
|
+
if ((options.format ?? "json") === "markdown") {
|
|
2552
|
+
return formatMarkdownReport(report);
|
|
2553
|
+
}
|
|
2554
|
+
return JSON.stringify(report, null, 2);
|
|
2555
|
+
}
|
|
2556
|
+
function assertDossierListOptions(options) {
|
|
2557
|
+
if (!isRecord8(options)) {
|
|
2558
|
+
throw new ProbeMeshError({
|
|
2559
|
+
code: "invalid_request",
|
|
2560
|
+
message: "Provider onboarding dossier import options must be an object."
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
if (!Array.isArray(options.dossiers)) {
|
|
2564
|
+
throw new ProbeMeshError({
|
|
2565
|
+
code: "invalid_request",
|
|
2566
|
+
message: "Provider onboarding dossier import options require dossiers array."
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
options.dossiers.forEach((dossier, index) => {
|
|
2570
|
+
const validation = validateProviderOnboardingDossier(dossier);
|
|
2571
|
+
if (!validation.valid) {
|
|
2572
|
+
throw new ProbeMeshError({
|
|
2573
|
+
code: "invalid_request",
|
|
2574
|
+
message: `Invalid provider onboarding dossier at index ${index}: ${formatValidationErrors3(
|
|
2575
|
+
validation.errors
|
|
2576
|
+
)}`
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
if (options.generatedAt !== void 0 && (typeof options.generatedAt !== "string" || Number.isNaN(Date.parse(options.generatedAt)))) {
|
|
2581
|
+
throw new ProbeMeshError({
|
|
2582
|
+
code: "invalid_request",
|
|
2583
|
+
message: "Provider onboarding dossier import generatedAt must be an ISO timestamp."
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
function assertEvaluationOptions2(options) {
|
|
2588
|
+
assertDossierListOptions(options);
|
|
2589
|
+
if (options.dossiers.length === 0) {
|
|
2590
|
+
throw new ProbeMeshError({
|
|
2591
|
+
code: "invalid_request",
|
|
2592
|
+
message: "Provider onboarding dossier evaluation requires at least one dossier."
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
if (options.policy === void 0) {
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
if (!isRecord8(options.policy)) {
|
|
2599
|
+
throw new ProbeMeshError({
|
|
2600
|
+
code: "invalid_request",
|
|
2601
|
+
message: "Provider onboarding dossier policy options must be an object."
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
if (!Array.isArray(options.policy.adapterInventory)) {
|
|
2605
|
+
throw new ProbeMeshError({
|
|
2606
|
+
code: "invalid_request",
|
|
2607
|
+
message: "Provider onboarding dossier policy requires adapterInventory array."
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
if (!Array.isArray(options.policy.queries) || options.policy.queries.length === 0) {
|
|
2611
|
+
throw new ProbeMeshError({
|
|
2612
|
+
code: "invalid_request",
|
|
2613
|
+
message: "Provider onboarding dossier policy requires at least one query."
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
function summarizeDossierImport(dossier) {
|
|
2618
|
+
return {
|
|
2619
|
+
dossierId: dossier.dossierId,
|
|
2620
|
+
providerId: dossier.provider.id,
|
|
2621
|
+
status: dossier.status,
|
|
2622
|
+
catalogArtifactId: dossier.evidence.catalog.artifactId,
|
|
2623
|
+
conformanceArtifactId: dossier.evidence.conformance.artifactId,
|
|
2624
|
+
imported: dossier.status === "ready",
|
|
2625
|
+
reasons: [...dossier.readiness.reasons],
|
|
2626
|
+
failedChecks: [...dossier.readiness.failedChecks]
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
function formatMarkdownReport(report) {
|
|
2630
|
+
const lines = [
|
|
2631
|
+
`# ${report.title}`,
|
|
2632
|
+
"",
|
|
2633
|
+
`Status: ${report.status}`,
|
|
2634
|
+
`Generated: ${report.generatedAt}`,
|
|
2635
|
+
"",
|
|
2636
|
+
"## Summary",
|
|
2637
|
+
`- Total dossiers: ${report.summary.totalDossiers}`,
|
|
2638
|
+
`- Ready dossiers: ${report.summary.readyDossiers}`,
|
|
2639
|
+
`- Failed dossiers: ${report.summary.failedDossiers}`,
|
|
2640
|
+
`- Imported dossiers: ${report.summary.importedDossiers}`,
|
|
2641
|
+
`- Excluded dossiers: ${report.summary.excludedDossiers}`,
|
|
2642
|
+
`- Catalog entries: ${report.summary.totalEntries}`,
|
|
2643
|
+
`- Providers: ${formatInlineList(report.summary.providers)}`,
|
|
2644
|
+
`- Capabilities: ${formatInlineList(report.summary.capabilities)}`,
|
|
2645
|
+
`- Policy status: ${report.summary.policyStatus ?? "not_run"}`,
|
|
2646
|
+
"",
|
|
2647
|
+
"## Checks",
|
|
2648
|
+
...report.checks.map(
|
|
2649
|
+
(check) => `- [${check.status === "passed" ? "x" : " "}] ${check.name}: ${check.message}`
|
|
2650
|
+
),
|
|
2651
|
+
"",
|
|
2652
|
+
"## Dossiers",
|
|
2653
|
+
...report.dossiers.map(
|
|
2654
|
+
(dossier) => `- ${dossier.dossierId}: ${dossier.status} / ${dossier.providerId} / ${dossier.imported ? "imported" : "excluded"}`
|
|
2655
|
+
)
|
|
2656
|
+
];
|
|
2657
|
+
if (report.policyEvaluation) {
|
|
2658
|
+
lines.push(
|
|
2659
|
+
"",
|
|
2660
|
+
"## Policy",
|
|
2661
|
+
`- Status: ${report.policyEvaluation.status}`,
|
|
2662
|
+
`- Routeable matches: ${report.policyEvaluation.summary.routeableMatches}`,
|
|
2663
|
+
`- Missing adapter matches: ${report.policyEvaluation.summary.missingAdapterMatches}`
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
return `${lines.join("\n")}
|
|
2667
|
+
`;
|
|
2668
|
+
}
|
|
2669
|
+
function sanitizeReport(value, forbiddenOutputValues) {
|
|
2670
|
+
const forbiddenValues = forbiddenOutputValues.filter((entry) => entry.length > 0);
|
|
2671
|
+
return redactX402Secrets(
|
|
2672
|
+
replaceForbiddenValues(value, forbiddenValues, /* @__PURE__ */ new WeakSet())
|
|
2673
|
+
);
|
|
2674
|
+
}
|
|
2675
|
+
function replaceForbiddenValues(value, forbiddenValues, seen) {
|
|
2676
|
+
if (typeof value === "string") {
|
|
2677
|
+
return forbiddenValues.reduce(
|
|
2678
|
+
(current, forbidden) => current.split(forbidden).join("[REDACTED]"),
|
|
2679
|
+
value
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
if (value === null || typeof value !== "object" || value instanceof Date) {
|
|
2683
|
+
return value;
|
|
2684
|
+
}
|
|
2685
|
+
if (seen.has(value)) {
|
|
2686
|
+
return "[Circular]";
|
|
2687
|
+
}
|
|
2688
|
+
seen.add(value);
|
|
2689
|
+
if (Array.isArray(value)) {
|
|
2690
|
+
const entries2 = value.map(
|
|
2691
|
+
(entry) => replaceForbiddenValues(entry, forbiddenValues, seen)
|
|
2692
|
+
);
|
|
2693
|
+
seen.delete(value);
|
|
2694
|
+
return entries2;
|
|
2695
|
+
}
|
|
2696
|
+
const entries = Object.fromEntries(
|
|
2697
|
+
Object.entries(value).map(([key, entry]) => [
|
|
2698
|
+
key,
|
|
2699
|
+
replaceForbiddenValues(entry, forbiddenValues, seen)
|
|
2700
|
+
])
|
|
2701
|
+
);
|
|
2702
|
+
seen.delete(value);
|
|
2703
|
+
return entries;
|
|
2704
|
+
}
|
|
2705
|
+
function cloneSerializable3(value) {
|
|
2706
|
+
if (value === void 0) {
|
|
2707
|
+
return value;
|
|
2708
|
+
}
|
|
2709
|
+
return JSON.parse(JSON.stringify(value));
|
|
2710
|
+
}
|
|
2711
|
+
function formatInlineList(values) {
|
|
2712
|
+
return values.length > 0 ? values.join(", ") : "none";
|
|
2713
|
+
}
|
|
2714
|
+
function isRecord8(value) {
|
|
2715
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2716
|
+
}
|
|
2717
|
+
function formatValidationErrors3(errors) {
|
|
2718
|
+
return errors.map((error) => `${error.path}: ${error.message}`).join("; ");
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// src/cli/providerDossierCheckCli.ts
|
|
2722
|
+
var USAGE = "Usage: probemesh-dossier-check --config <path> [--format markdown|json] [--out-json <path>] [--out-md <path>] [--out-catalog <path>] [--title <text>] [--generated-at <iso>]";
|
|
2723
|
+
async function runProviderDossierCheckCli(options = {}) {
|
|
2724
|
+
const argv = options.argv ?? [];
|
|
2725
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2726
|
+
try {
|
|
2727
|
+
const parsedArgs = parseCliArgs(argv);
|
|
2728
|
+
if (parsedArgs.help) {
|
|
2729
|
+
return {
|
|
2730
|
+
exitCode: 0,
|
|
2731
|
+
skipped: true,
|
|
2732
|
+
stdout: `${USAGE}
|
|
2733
|
+
`
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2736
|
+
if (!parsedArgs.configPath) {
|
|
2737
|
+
return cliFailure("--config <path> is required.");
|
|
2738
|
+
}
|
|
2739
|
+
const configExport = await loadCliConfig(parsedArgs.configPath, {
|
|
2740
|
+
argv,
|
|
2741
|
+
cwd
|
|
2742
|
+
});
|
|
2743
|
+
if (isCliSkip(configExport)) {
|
|
2744
|
+
return {
|
|
2745
|
+
exitCode: 0,
|
|
2746
|
+
skipped: true,
|
|
2747
|
+
skip: configExport,
|
|
2748
|
+
stdout: `provider dossier check skipped: ${configExport.reason}
|
|
2749
|
+
`
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
assertCliConfig(configExport);
|
|
2753
|
+
const dossiers = await resolveDossiers(configExport, cwd);
|
|
2754
|
+
const evaluation = evaluateProviderOnboardingDossiers({
|
|
2755
|
+
dossiers,
|
|
2756
|
+
generatedAt: parsedArgs.generatedAt ?? configExport.catalog?.generatedAt,
|
|
2757
|
+
policy: configExport.policy
|
|
2758
|
+
});
|
|
2759
|
+
const report = createProviderOnboardingDossierImportReport(evaluation, {
|
|
2760
|
+
...configExport.report ?? {},
|
|
2761
|
+
title: parsedArgs.title ?? configExport.report?.title,
|
|
2762
|
+
generatedAt: parsedArgs.generatedAt ?? configExport.report?.generatedAt
|
|
2763
|
+
});
|
|
2764
|
+
const selectedFormat = parsedArgs.format ?? configExport.report?.format ?? "markdown";
|
|
2765
|
+
const stdout = formatProviderOnboardingDossierImportReport(report, {
|
|
2766
|
+
format: selectedFormat
|
|
2767
|
+
});
|
|
2768
|
+
const artifactPaths = await writeArtifacts({
|
|
2769
|
+
cwd,
|
|
2770
|
+
report,
|
|
2771
|
+
catalog: evaluation.catalog,
|
|
2772
|
+
outputs: {
|
|
2773
|
+
json: parsedArgs.outJson ?? configExport.outputs?.json,
|
|
2774
|
+
markdown: parsedArgs.outMarkdown ?? configExport.outputs?.markdown,
|
|
2775
|
+
catalog: parsedArgs.outCatalog ?? configExport.outputs?.catalog
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
return {
|
|
2779
|
+
exitCode: evaluation.status === "passed" ? 0 : 1,
|
|
2780
|
+
skipped: false,
|
|
2781
|
+
evaluation,
|
|
2782
|
+
report,
|
|
2783
|
+
catalog: evaluation.catalog,
|
|
2784
|
+
stdout,
|
|
2785
|
+
artifactPaths
|
|
2786
|
+
};
|
|
2787
|
+
} catch (error) {
|
|
2788
|
+
return cliFailure(
|
|
2789
|
+
error instanceof Error ? error.message : "Unknown provider dossier check CLI failure."
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
function parseCliArgs(argv) {
|
|
2794
|
+
const parsedArgs = {};
|
|
2795
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2796
|
+
const arg = argv[index];
|
|
2797
|
+
if (arg === "--") {
|
|
2798
|
+
continue;
|
|
2799
|
+
}
|
|
2800
|
+
if (arg === "--help" || arg === "-h") {
|
|
2801
|
+
parsedArgs.help = true;
|
|
2802
|
+
continue;
|
|
2803
|
+
}
|
|
2804
|
+
if (arg === "--config") {
|
|
2805
|
+
parsedArgs.configPath = requireValue(argv, index, arg);
|
|
2806
|
+
index += 1;
|
|
2807
|
+
continue;
|
|
2808
|
+
}
|
|
2809
|
+
if (arg === "--format") {
|
|
2810
|
+
const format = requireValue(argv, index, arg);
|
|
2811
|
+
if (format !== "markdown" && format !== "json") {
|
|
2812
|
+
throw new Error("--format must be markdown or json.");
|
|
2813
|
+
}
|
|
2814
|
+
parsedArgs.format = format;
|
|
2815
|
+
index += 1;
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
if (arg === "--out-json") {
|
|
2819
|
+
parsedArgs.outJson = requireValue(argv, index, arg);
|
|
2820
|
+
index += 1;
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
if (arg === "--out-md") {
|
|
2824
|
+
parsedArgs.outMarkdown = requireValue(argv, index, arg);
|
|
2825
|
+
index += 1;
|
|
2826
|
+
continue;
|
|
2827
|
+
}
|
|
2828
|
+
if (arg === "--out-catalog") {
|
|
2829
|
+
parsedArgs.outCatalog = requireValue(argv, index, arg);
|
|
2830
|
+
index += 1;
|
|
2831
|
+
continue;
|
|
2832
|
+
}
|
|
2833
|
+
if (arg === "--title") {
|
|
2834
|
+
parsedArgs.title = requireValue(argv, index, arg);
|
|
2835
|
+
index += 1;
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
if (arg === "--generated-at") {
|
|
2839
|
+
parsedArgs.generatedAt = requireValue(argv, index, arg);
|
|
2840
|
+
index += 1;
|
|
2841
|
+
continue;
|
|
2842
|
+
}
|
|
2843
|
+
throw new Error(`Unknown argument "${arg}".`);
|
|
2844
|
+
}
|
|
2845
|
+
return parsedArgs;
|
|
2846
|
+
}
|
|
2847
|
+
function requireValue(argv, index, flag) {
|
|
2848
|
+
const value = argv[index + 1];
|
|
2849
|
+
if (!value || value.startsWith("--")) {
|
|
2850
|
+
throw new Error(`${flag} requires a value.`);
|
|
2851
|
+
}
|
|
2852
|
+
return value;
|
|
2853
|
+
}
|
|
2854
|
+
async function loadCliConfig(configPath, context) {
|
|
2855
|
+
const configExport = await loadCliConfigDefault(configPath, context.cwd);
|
|
2856
|
+
if (typeof configExport === "function") {
|
|
2857
|
+
return configExport(context);
|
|
2858
|
+
}
|
|
2859
|
+
return configExport;
|
|
2860
|
+
}
|
|
2861
|
+
function isCliSkip(value) {
|
|
2862
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && value.skip === true && typeof value.reason === "string";
|
|
2863
|
+
}
|
|
2864
|
+
function assertCliConfig(config) {
|
|
2865
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
2866
|
+
throw new Error("Provider dossier check CLI config must be an object.");
|
|
2867
|
+
}
|
|
2868
|
+
const candidate = config;
|
|
2869
|
+
if (candidate.dossiers !== void 0 && !Array.isArray(candidate.dossiers)) {
|
|
2870
|
+
throw new Error("Provider dossier check CLI dossiers must be an array.");
|
|
2871
|
+
}
|
|
2872
|
+
if (candidate.dossierFiles !== void 0 && (!Array.isArray(candidate.dossierFiles) || candidate.dossierFiles.some((file) => typeof file !== "string"))) {
|
|
2873
|
+
throw new Error("Provider dossier check CLI dossierFiles must be an array of strings.");
|
|
2874
|
+
}
|
|
2875
|
+
if ((candidate.dossiers?.length ?? 0) === 0 && (candidate.dossierFiles?.length ?? 0) === 0) {
|
|
2876
|
+
throw new Error("Provider dossier check CLI config requires dossiers or dossierFiles.");
|
|
2877
|
+
}
|
|
2878
|
+
if (candidate.catalog !== void 0 && !isRecord9(candidate.catalog)) {
|
|
2879
|
+
throw new Error("Provider dossier check CLI catalog config must be an object.");
|
|
2880
|
+
}
|
|
2881
|
+
if (candidate.policy !== void 0 && !isRecord9(candidate.policy)) {
|
|
2882
|
+
throw new Error("Provider dossier check CLI policy config must be an object.");
|
|
2883
|
+
}
|
|
2884
|
+
if (candidate.outputs !== void 0) {
|
|
2885
|
+
assertOutputs(candidate.outputs);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
function assertOutputs(outputs) {
|
|
2889
|
+
if (!isRecord9(outputs)) {
|
|
2890
|
+
throw new Error("Provider dossier check CLI outputs must be an object.");
|
|
2891
|
+
}
|
|
2892
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
2893
|
+
if (!["json", "markdown", "catalog"].includes(key) || value !== void 0 && typeof value !== "string") {
|
|
2894
|
+
throw new Error(
|
|
2895
|
+
"Provider dossier check CLI outputs may only include json, markdown, and catalog string paths."
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
async function resolveDossiers(config, cwd) {
|
|
2901
|
+
const dossiers = [
|
|
2902
|
+
...config.dossiers ?? []
|
|
2903
|
+
];
|
|
2904
|
+
for (const dossierFile of config.dossierFiles ?? []) {
|
|
2905
|
+
const parsed = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.resolve)(cwd, dossierFile), "utf8"));
|
|
2906
|
+
const entries = Array.isArray(parsed) ? parsed : [parsed];
|
|
2907
|
+
for (const entry of entries) {
|
|
2908
|
+
dossiers.push(parseProviderOnboardingDossierJson(JSON.stringify(entry)));
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
return dossiers;
|
|
2912
|
+
}
|
|
2913
|
+
async function writeArtifacts(options) {
|
|
2914
|
+
const artifactPaths = {};
|
|
2915
|
+
if (options.outputs.json) {
|
|
2916
|
+
const path = (0, import_node_path2.resolve)(options.cwd, options.outputs.json);
|
|
2917
|
+
await writeTextFile(
|
|
2918
|
+
path,
|
|
2919
|
+
formatProviderOnboardingDossierImportReport(options.report, {
|
|
2920
|
+
format: "json"
|
|
2921
|
+
})
|
|
2922
|
+
);
|
|
2923
|
+
artifactPaths.json = path;
|
|
2924
|
+
}
|
|
2925
|
+
if (options.outputs.markdown) {
|
|
2926
|
+
const path = (0, import_node_path2.resolve)(options.cwd, options.outputs.markdown);
|
|
2927
|
+
await writeTextFile(
|
|
2928
|
+
path,
|
|
2929
|
+
formatProviderOnboardingDossierImportReport(options.report, {
|
|
2930
|
+
format: "markdown"
|
|
2931
|
+
})
|
|
2932
|
+
);
|
|
2933
|
+
artifactPaths.markdown = path;
|
|
2934
|
+
}
|
|
2935
|
+
if (options.outputs.catalog) {
|
|
2936
|
+
const path = (0, import_node_path2.resolve)(options.cwd, options.outputs.catalog);
|
|
2937
|
+
await writeTextFile(path, `${JSON.stringify(options.catalog, null, 2)}
|
|
2938
|
+
`);
|
|
2939
|
+
artifactPaths.catalog = path;
|
|
2940
|
+
}
|
|
2941
|
+
return artifactPaths;
|
|
2942
|
+
}
|
|
2943
|
+
async function writeTextFile(path, body) {
|
|
2944
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(path), {
|
|
2945
|
+
recursive: true
|
|
2946
|
+
});
|
|
2947
|
+
await (0, import_promises2.writeFile)(path, body, "utf8");
|
|
2948
|
+
}
|
|
2949
|
+
function cliFailure(message) {
|
|
2950
|
+
return {
|
|
2951
|
+
exitCode: 2,
|
|
2952
|
+
skipped: false,
|
|
2953
|
+
stdout: "",
|
|
2954
|
+
stderr: `Provider dossier check CLI failed: ${message}
|
|
2955
|
+
${USAGE}
|
|
2956
|
+
`
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
function isRecord9(value) {
|
|
2960
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// src/cli/provider-dossier-check.ts
|
|
2964
|
+
void main();
|
|
2965
|
+
async function main() {
|
|
2966
|
+
const result = await runProviderDossierCheckCli({
|
|
2967
|
+
argv: process.argv.slice(2),
|
|
2968
|
+
cwd: process.cwd()
|
|
2969
|
+
});
|
|
2970
|
+
if (result.stdout) {
|
|
2971
|
+
process.stdout.write(result.stdout);
|
|
2972
|
+
}
|
|
2973
|
+
if (result.stderr) {
|
|
2974
|
+
process.stderr.write(result.stderr);
|
|
2975
|
+
}
|
|
2976
|
+
process.exitCode = result.exitCode;
|
|
2977
|
+
}
|
|
2978
|
+
//# sourceMappingURL=provider-dossier-check.cjs.map
|