@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,705 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertProbeMeshCallAuditArtifact,
|
|
3
|
+
validateProbeMeshCallAuditArtifact
|
|
4
|
+
} from "./chunk-5Q3PDYIA.js";
|
|
5
|
+
import {
|
|
6
|
+
loadCliConfigDefault,
|
|
7
|
+
redactX402Secrets
|
|
8
|
+
} from "./chunk-NYTV263W.js";
|
|
9
|
+
import {
|
|
10
|
+
ProbeMeshError
|
|
11
|
+
} from "./chunk-2ASMVLG4.js";
|
|
12
|
+
|
|
13
|
+
// src/auditTrailBundle.ts
|
|
14
|
+
import { createHash } from "crypto";
|
|
15
|
+
var PROBEMESH_AUDIT_TRAIL_BUNDLE_SCHEMA_VERSION = "probemesh.audit-trail.v1";
|
|
16
|
+
function createProbeMeshAuditTrailCollector() {
|
|
17
|
+
const artifacts = [];
|
|
18
|
+
return {
|
|
19
|
+
onArtifact(artifact) {
|
|
20
|
+
assertProbeMeshCallAuditArtifact(artifact);
|
|
21
|
+
artifacts.push(cloneSerializable(artifact));
|
|
22
|
+
},
|
|
23
|
+
getArtifacts() {
|
|
24
|
+
return cloneSerializable(artifacts);
|
|
25
|
+
},
|
|
26
|
+
clear() {
|
|
27
|
+
artifacts.length = 0;
|
|
28
|
+
},
|
|
29
|
+
createBundle(options = {}) {
|
|
30
|
+
return createProbeMeshAuditTrailBundle({
|
|
31
|
+
...options,
|
|
32
|
+
artifacts
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createProbeMeshAuditTrailBundle(options) {
|
|
38
|
+
assertBundleOptions(options);
|
|
39
|
+
for (const [index, artifact] of options.artifacts.entries()) {
|
|
40
|
+
const validation = validateProbeMeshCallAuditArtifact(artifact);
|
|
41
|
+
if (!validation.valid) {
|
|
42
|
+
throw new ProbeMeshError({
|
|
43
|
+
code: "invalid_request",
|
|
44
|
+
message: `Invalid ProbeMesh call audit artifact at index ${index}: ${formatValidationErrors(
|
|
45
|
+
validation.errors
|
|
46
|
+
)}`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const artifacts = sanitizeBundleOutput(
|
|
51
|
+
options.artifacts,
|
|
52
|
+
options.forbiddenOutputValues ?? []
|
|
53
|
+
);
|
|
54
|
+
const summary = summarizeArtifacts(artifacts);
|
|
55
|
+
const hashesWithoutBundle = {
|
|
56
|
+
artifacts: hashStableJson(artifacts),
|
|
57
|
+
summary: hashStableJson(summary),
|
|
58
|
+
bundle: ""
|
|
59
|
+
};
|
|
60
|
+
const bundleBase = {
|
|
61
|
+
schemaVersion: PROBEMESH_AUDIT_TRAIL_BUNDLE_SCHEMA_VERSION,
|
|
62
|
+
bundleId: options.bundleId ?? `audit_trail_${hashStableJson({
|
|
63
|
+
artifacts: hashesWithoutBundle.artifacts,
|
|
64
|
+
summary: hashesWithoutBundle.summary
|
|
65
|
+
}).slice(0, 16)}`,
|
|
66
|
+
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
67
|
+
status: artifacts.length > 0 ? "complete" : "incomplete",
|
|
68
|
+
hashAlgorithm: "sha256",
|
|
69
|
+
hashes: hashesWithoutBundle,
|
|
70
|
+
summary,
|
|
71
|
+
artifacts
|
|
72
|
+
};
|
|
73
|
+
const bundle = {
|
|
74
|
+
...bundleBase,
|
|
75
|
+
hashes: {
|
|
76
|
+
...hashesWithoutBundle,
|
|
77
|
+
bundle: hashBundleContent(bundleBase)
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
assertProbeMeshAuditTrailBundle(bundle);
|
|
81
|
+
return bundle;
|
|
82
|
+
}
|
|
83
|
+
function summarizeProbeMeshAuditTrailBundle(bundleOrArtifacts) {
|
|
84
|
+
return Array.isArray(bundleOrArtifacts) ? summarizeArtifacts(bundleOrArtifacts) : cloneSerializable(bundleOrArtifacts.summary);
|
|
85
|
+
}
|
|
86
|
+
function validateProbeMeshAuditTrailBundle(bundle) {
|
|
87
|
+
const errors = [];
|
|
88
|
+
if (!isRecord(bundle)) {
|
|
89
|
+
return {
|
|
90
|
+
valid: false,
|
|
91
|
+
errors: [
|
|
92
|
+
{
|
|
93
|
+
path: "$",
|
|
94
|
+
message: "ProbeMesh audit trail bundle must be an object."
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (bundle.schemaVersion !== PROBEMESH_AUDIT_TRAIL_BUNDLE_SCHEMA_VERSION) {
|
|
100
|
+
errors.push({
|
|
101
|
+
path: "schemaVersion",
|
|
102
|
+
message: `schemaVersion must be "${PROBEMESH_AUDIT_TRAIL_BUNDLE_SCHEMA_VERSION}".`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
validateNonEmptyString(bundle.bundleId, "bundleId", errors);
|
|
106
|
+
validateIsoTimestamp(bundle.generatedAt, "generatedAt", errors);
|
|
107
|
+
if (bundle.status !== "complete" && bundle.status !== "incomplete") {
|
|
108
|
+
errors.push({
|
|
109
|
+
path: "status",
|
|
110
|
+
message: 'status must be "complete" or "incomplete".'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (bundle.hashAlgorithm !== "sha256") {
|
|
114
|
+
errors.push({
|
|
115
|
+
path: "hashAlgorithm",
|
|
116
|
+
message: 'hashAlgorithm must be "sha256".'
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
validateHashes(bundle.hashes, errors);
|
|
120
|
+
validateSummary(bundle.summary, errors);
|
|
121
|
+
if (!Array.isArray(bundle.artifacts)) {
|
|
122
|
+
errors.push({
|
|
123
|
+
path: "artifacts",
|
|
124
|
+
message: "artifacts must be an array."
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
bundle.artifacts.forEach((artifact, index) => {
|
|
128
|
+
const validation = validateProbeMeshCallAuditArtifact(artifact);
|
|
129
|
+
for (const artifactError of validation.errors) {
|
|
130
|
+
errors.push({
|
|
131
|
+
path: `artifacts.${index}.${artifactError.path}`,
|
|
132
|
+
message: artifactError.message
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (errors.length === 0) {
|
|
138
|
+
const typedBundle = bundle;
|
|
139
|
+
pushHashMismatch(
|
|
140
|
+
errors,
|
|
141
|
+
"hashes.artifacts",
|
|
142
|
+
typedBundle.hashes.artifacts,
|
|
143
|
+
hashStableJson(typedBundle.artifacts)
|
|
144
|
+
);
|
|
145
|
+
pushHashMismatch(
|
|
146
|
+
errors,
|
|
147
|
+
"hashes.summary",
|
|
148
|
+
typedBundle.hashes.summary,
|
|
149
|
+
hashStableJson(typedBundle.summary)
|
|
150
|
+
);
|
|
151
|
+
pushHashMismatch(
|
|
152
|
+
errors,
|
|
153
|
+
"hashes.bundle",
|
|
154
|
+
typedBundle.hashes.bundle,
|
|
155
|
+
hashBundleContent(typedBundle)
|
|
156
|
+
);
|
|
157
|
+
const expectedSummary = summarizeArtifacts(typedBundle.artifacts);
|
|
158
|
+
if (stableStringify(typedBundle.summary) !== stableStringify(expectedSummary)) {
|
|
159
|
+
errors.push({
|
|
160
|
+
path: "summary",
|
|
161
|
+
message: "summary does not match bundle artifacts."
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
valid: errors.length === 0,
|
|
167
|
+
errors
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function assertProbeMeshAuditTrailBundle(bundle) {
|
|
171
|
+
const result = validateProbeMeshAuditTrailBundle(bundle);
|
|
172
|
+
if (result.valid) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
throw new ProbeMeshError({
|
|
176
|
+
code: "invalid_request",
|
|
177
|
+
message: `Invalid ProbeMesh audit trail bundle: ${formatValidationErrors(
|
|
178
|
+
result.errors
|
|
179
|
+
)}`
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function formatProbeMeshAuditTrailBundle(bundle, options = {}) {
|
|
183
|
+
assertProbeMeshAuditTrailBundle(bundle);
|
|
184
|
+
if ((options.format ?? "json") === "markdown") {
|
|
185
|
+
return formatBundleMarkdown(bundle);
|
|
186
|
+
}
|
|
187
|
+
return JSON.stringify(bundle, null, 2);
|
|
188
|
+
}
|
|
189
|
+
function assertBundleOptions(options) {
|
|
190
|
+
if (!isRecord(options)) {
|
|
191
|
+
throw new ProbeMeshError({
|
|
192
|
+
code: "invalid_request",
|
|
193
|
+
message: "ProbeMesh audit trail bundle options must be an object."
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (!Array.isArray(options.artifacts)) {
|
|
197
|
+
throw new ProbeMeshError({
|
|
198
|
+
code: "invalid_request",
|
|
199
|
+
message: "ProbeMesh audit trail bundle artifacts must be an array."
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (options.generatedAt !== void 0 && (typeof options.generatedAt !== "string" || Number.isNaN(Date.parse(options.generatedAt)))) {
|
|
203
|
+
throw new ProbeMeshError({
|
|
204
|
+
code: "invalid_request",
|
|
205
|
+
message: "ProbeMesh audit trail bundle generatedAt must be an ISO timestamp."
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (options.bundleId !== void 0 && (typeof options.bundleId !== "string" || options.bundleId.length === 0)) {
|
|
209
|
+
throw new ProbeMeshError({
|
|
210
|
+
code: "invalid_request",
|
|
211
|
+
message: "ProbeMesh audit trail bundleId must be a non-empty string."
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (options.forbiddenOutputValues !== void 0 && (!Array.isArray(options.forbiddenOutputValues) || options.forbiddenOutputValues.some((value) => typeof value !== "string"))) {
|
|
215
|
+
throw new ProbeMeshError({
|
|
216
|
+
code: "invalid_request",
|
|
217
|
+
message: "ProbeMesh audit trail bundle forbiddenOutputValues must be an array of strings."
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function summarizeArtifacts(artifacts) {
|
|
222
|
+
const providers = [];
|
|
223
|
+
const capabilities = [];
|
|
224
|
+
const receiptTypes = [];
|
|
225
|
+
const policyBundleIds = [];
|
|
226
|
+
let unsafePaymentEvidenceCalls = 0;
|
|
227
|
+
let retrySafeFailures = 0;
|
|
228
|
+
let uncoveredPolicyFailures = 0;
|
|
229
|
+
for (const artifact of artifacts) {
|
|
230
|
+
pushUnique(providers, artifact.call.providerId);
|
|
231
|
+
pushUnique(capabilities, artifact.call.capability);
|
|
232
|
+
for (const receiptType of artifact.evidence.receiptSummary.types) {
|
|
233
|
+
pushUnique(receiptTypes, receiptType);
|
|
234
|
+
}
|
|
235
|
+
const attestationBundleId = artifact.evidence.route?.attestation?.bundleId;
|
|
236
|
+
pushUnique(policyBundleIds, attestationBundleId);
|
|
237
|
+
if (artifact.evidence.safety?.moneyMayHaveMoved === true || artifact.evidence.receiptSummary.hasPaymentProof) {
|
|
238
|
+
unsafePaymentEvidenceCalls += 1;
|
|
239
|
+
}
|
|
240
|
+
if (artifact.status === "failed" && artifact.evidence.retrySafety?.safeToAutoRetry === true) {
|
|
241
|
+
retrySafeFailures += 1;
|
|
242
|
+
}
|
|
243
|
+
if (artifact.status === "failed" && artifact.evidence.route?.explanation?.attempts.some(
|
|
244
|
+
(attempt) => attempt.reason === "skipped_policy_bundle_uncovered"
|
|
245
|
+
) === true) {
|
|
246
|
+
uncoveredPolicyFailures += 1;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
totalCalls: artifacts.length,
|
|
251
|
+
completedCalls: artifacts.filter((artifact) => artifact.status === "completed").length,
|
|
252
|
+
failedCalls: artifacts.filter((artifact) => artifact.status === "failed").length,
|
|
253
|
+
providers,
|
|
254
|
+
capabilities,
|
|
255
|
+
receiptTypes,
|
|
256
|
+
policyBundleIds,
|
|
257
|
+
unsafePaymentEvidenceCalls,
|
|
258
|
+
retrySafeFailures,
|
|
259
|
+
uncoveredPolicyFailures
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function validateHashes(hashes, errors) {
|
|
263
|
+
if (!isRecord(hashes)) {
|
|
264
|
+
errors.push({
|
|
265
|
+
path: "hashes",
|
|
266
|
+
message: "hashes must be an object."
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
for (const key of ["artifacts", "summary", "bundle"]) {
|
|
271
|
+
validateSha256String(hashes[key], `hashes.${key}`, errors);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function validateSummary(summary, errors) {
|
|
275
|
+
if (!isRecord(summary)) {
|
|
276
|
+
errors.push({
|
|
277
|
+
path: "summary",
|
|
278
|
+
message: "summary must be an object."
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
for (const key of [
|
|
283
|
+
"totalCalls",
|
|
284
|
+
"completedCalls",
|
|
285
|
+
"failedCalls",
|
|
286
|
+
"unsafePaymentEvidenceCalls",
|
|
287
|
+
"retrySafeFailures",
|
|
288
|
+
"uncoveredPolicyFailures"
|
|
289
|
+
]) {
|
|
290
|
+
if (typeof summary[key] !== "number" || !Number.isFinite(summary[key]) || summary[key] < 0) {
|
|
291
|
+
errors.push({
|
|
292
|
+
path: `summary.${key}`,
|
|
293
|
+
message: `summary.${key} must be a non-negative finite number.`
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
validateStringArray(summary.providers, "summary.providers", errors);
|
|
298
|
+
validateStringArray(summary.capabilities, "summary.capabilities", errors);
|
|
299
|
+
validateStringArray(summary.receiptTypes, "summary.receiptTypes", errors);
|
|
300
|
+
validateStringArray(summary.policyBundleIds, "summary.policyBundleIds", errors);
|
|
301
|
+
}
|
|
302
|
+
function hashBundleContent(bundle) {
|
|
303
|
+
return hashStableJson({
|
|
304
|
+
...bundle,
|
|
305
|
+
hashes: {
|
|
306
|
+
...bundle.hashes,
|
|
307
|
+
bundle: ""
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
function pushHashMismatch(errors, path, expected, actual) {
|
|
312
|
+
if (expected === actual) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
errors.push({
|
|
316
|
+
path,
|
|
317
|
+
message: `${path} does not match bundle content.`
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function hashStableJson(value) {
|
|
321
|
+
return createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
322
|
+
}
|
|
323
|
+
function stableStringify(value) {
|
|
324
|
+
return JSON.stringify(sortSerializable(value));
|
|
325
|
+
}
|
|
326
|
+
function sortSerializable(value) {
|
|
327
|
+
if (Array.isArray(value)) {
|
|
328
|
+
return value.map(sortSerializable);
|
|
329
|
+
}
|
|
330
|
+
if (!isRecord(value)) {
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
return Object.fromEntries(
|
|
334
|
+
Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => [key, sortSerializable(value[key])])
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
function sanitizeBundleOutput(value, forbiddenOutputValues) {
|
|
338
|
+
const forbiddenValues = forbiddenOutputValues.filter((entry) => entry.length > 0);
|
|
339
|
+
return redactX402Secrets(
|
|
340
|
+
replaceForbiddenValues(cloneSerializable(value), forbiddenValues)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
function replaceForbiddenValues(value, forbiddenValues) {
|
|
344
|
+
if (typeof value === "string") {
|
|
345
|
+
return forbiddenValues.reduce(
|
|
346
|
+
(current, forbidden) => current.split(forbidden).join("[REDACTED]"),
|
|
347
|
+
value
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (value === null || typeof value !== "object" || value instanceof Date) {
|
|
351
|
+
return value;
|
|
352
|
+
}
|
|
353
|
+
if (Array.isArray(value)) {
|
|
354
|
+
return value.map((entry) => replaceForbiddenValues(entry, forbiddenValues));
|
|
355
|
+
}
|
|
356
|
+
return Object.fromEntries(
|
|
357
|
+
Object.entries(value).map(([key, entry]) => [
|
|
358
|
+
key,
|
|
359
|
+
replaceForbiddenValues(entry, forbiddenValues)
|
|
360
|
+
])
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
function cloneSerializable(value) {
|
|
364
|
+
if (value === void 0) {
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
return JSON.parse(JSON.stringify(value));
|
|
368
|
+
}
|
|
369
|
+
function formatBundleMarkdown(bundle) {
|
|
370
|
+
return [
|
|
371
|
+
`# ProbeMesh Audit Trail Bundle`,
|
|
372
|
+
``,
|
|
373
|
+
`Status: ${bundle.status}`,
|
|
374
|
+
`Schema: ${bundle.schemaVersion}`,
|
|
375
|
+
`Bundle ID: ${bundle.bundleId}`,
|
|
376
|
+
`Generated: ${bundle.generatedAt}`,
|
|
377
|
+
``,
|
|
378
|
+
`## Summary`,
|
|
379
|
+
`- Total calls: ${bundle.summary.totalCalls}`,
|
|
380
|
+
`- Completed: ${bundle.summary.completedCalls}`,
|
|
381
|
+
`- Failed: ${bundle.summary.failedCalls}`,
|
|
382
|
+
`- Providers: ${formatInlineList(bundle.summary.providers)}`,
|
|
383
|
+
`- Capabilities: ${formatInlineList(bundle.summary.capabilities)}`,
|
|
384
|
+
`- Receipt types: ${formatInlineList(bundle.summary.receiptTypes)}`,
|
|
385
|
+
`- Policy bundles: ${formatInlineList(bundle.summary.policyBundleIds)}`,
|
|
386
|
+
`- Unsafe payment evidence calls: ${bundle.summary.unsafePaymentEvidenceCalls}`,
|
|
387
|
+
`- Retry-safe failures: ${bundle.summary.retrySafeFailures}`,
|
|
388
|
+
`- Uncovered policy failures: ${bundle.summary.uncoveredPolicyFailures}`,
|
|
389
|
+
``,
|
|
390
|
+
`## Artifacts`,
|
|
391
|
+
...bundle.artifacts.map(
|
|
392
|
+
(artifact) => `- ${artifact.call.callId ?? artifact.artifactId}: ${artifact.status} / ${artifact.call.capability} / ${artifact.call.providerId ?? "none"}`
|
|
393
|
+
),
|
|
394
|
+
``,
|
|
395
|
+
`## Hashes`,
|
|
396
|
+
`- Bundle: ${bundle.hashes.bundle}`,
|
|
397
|
+
`- Artifacts: ${bundle.hashes.artifacts}`,
|
|
398
|
+
`- Summary: ${bundle.hashes.summary}`
|
|
399
|
+
].join("\n");
|
|
400
|
+
}
|
|
401
|
+
function formatInlineList(values) {
|
|
402
|
+
return values.length === 0 ? "none" : values.join(", ");
|
|
403
|
+
}
|
|
404
|
+
function pushUnique(values, value) {
|
|
405
|
+
if (value && !values.includes(value)) {
|
|
406
|
+
values.push(value);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function validateSha256String(value, path, errors) {
|
|
410
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) {
|
|
411
|
+
errors.push({
|
|
412
|
+
path,
|
|
413
|
+
message: `${path} must be a sha256 hex string.`
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function validateNonEmptyString(value, path, errors) {
|
|
418
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
419
|
+
errors.push({
|
|
420
|
+
path,
|
|
421
|
+
message: `${path} must be a non-empty string.`
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function validateIsoTimestamp(value, path, errors) {
|
|
426
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
427
|
+
errors.push({
|
|
428
|
+
path,
|
|
429
|
+
message: `${path} must be an ISO timestamp.`
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function validateStringArray(value, path, errors) {
|
|
434
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
435
|
+
errors.push({
|
|
436
|
+
path,
|
|
437
|
+
message: `${path} must be an array of non-empty strings.`
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function isRecord(value) {
|
|
442
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
443
|
+
}
|
|
444
|
+
function formatValidationErrors(errors) {
|
|
445
|
+
return errors.map((error) => `${error.path}: ${error.message}`).join("; ");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/cli/auditTrailExportCli.ts
|
|
449
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
450
|
+
import { dirname, resolve } from "path";
|
|
451
|
+
var USAGE = "Usage: probemesh-audit-trail-export --config <path> [--format markdown|json] [--out-json <path>] [--out-md <path>] [--bundle-id <text>] [--generated-at <iso>]";
|
|
452
|
+
async function runProbeMeshAuditTrailExportCli(options = {}) {
|
|
453
|
+
const argv = options.argv ?? [];
|
|
454
|
+
const cwd = options.cwd ?? process.cwd();
|
|
455
|
+
try {
|
|
456
|
+
const parsedArgs = parseCliArgs(argv);
|
|
457
|
+
if (parsedArgs.help) {
|
|
458
|
+
return {
|
|
459
|
+
exitCode: 0,
|
|
460
|
+
skipped: true,
|
|
461
|
+
stdout: `${USAGE}
|
|
462
|
+
`
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (!parsedArgs.configPath) {
|
|
466
|
+
return cliFailure("--config <path> is required.");
|
|
467
|
+
}
|
|
468
|
+
const configExport = await loadCliConfig(parsedArgs.configPath, {
|
|
469
|
+
argv,
|
|
470
|
+
cwd
|
|
471
|
+
});
|
|
472
|
+
if (isCliSkip(configExport)) {
|
|
473
|
+
return {
|
|
474
|
+
exitCode: 0,
|
|
475
|
+
skipped: true,
|
|
476
|
+
skip: configExport,
|
|
477
|
+
stdout: `ProbeMesh audit trail export skipped: ${configExport.reason}
|
|
478
|
+
`
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
assertCliConfig(configExport);
|
|
482
|
+
const artifacts = await loadArtifacts({
|
|
483
|
+
cwd,
|
|
484
|
+
inlineArtifacts: configExport.artifacts,
|
|
485
|
+
artifactFiles: configExport.artifactFiles
|
|
486
|
+
});
|
|
487
|
+
const bundle = createProbeMeshAuditTrailBundle({
|
|
488
|
+
artifacts,
|
|
489
|
+
bundleId: parsedArgs.bundleId ?? configExport.bundle?.bundleId,
|
|
490
|
+
generatedAt: parsedArgs.generatedAt ?? configExport.bundle?.generatedAt,
|
|
491
|
+
forbiddenOutputValues: configExport.bundle?.forbiddenOutputValues
|
|
492
|
+
});
|
|
493
|
+
const selectedFormat = parsedArgs.format ?? configExport.bundle?.format ?? "markdown";
|
|
494
|
+
const stdout = formatProbeMeshAuditTrailBundle(bundle, {
|
|
495
|
+
format: selectedFormat
|
|
496
|
+
});
|
|
497
|
+
const artifactPaths = await writeArtifacts({
|
|
498
|
+
cwd,
|
|
499
|
+
bundle,
|
|
500
|
+
outputs: {
|
|
501
|
+
json: parsedArgs.outJson ?? configExport.outputs?.json,
|
|
502
|
+
markdown: parsedArgs.outMarkdown ?? configExport.outputs?.markdown
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
return {
|
|
506
|
+
exitCode: bundle.status === "complete" ? 0 : 1,
|
|
507
|
+
skipped: false,
|
|
508
|
+
bundle,
|
|
509
|
+
stdout,
|
|
510
|
+
artifactPaths
|
|
511
|
+
};
|
|
512
|
+
} catch (error) {
|
|
513
|
+
return cliFailure(
|
|
514
|
+
error instanceof Error ? error.message : "Unknown audit trail export CLI failure."
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function parseCliArgs(argv) {
|
|
519
|
+
const parsedArgs = {};
|
|
520
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
521
|
+
const arg = argv[index];
|
|
522
|
+
if (arg === "--") {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (arg === "--help" || arg === "-h") {
|
|
526
|
+
parsedArgs.help = true;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (arg === "--config") {
|
|
530
|
+
parsedArgs.configPath = requireValue(argv, index, arg);
|
|
531
|
+
index += 1;
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
if (arg === "--format") {
|
|
535
|
+
const format = requireValue(argv, index, arg);
|
|
536
|
+
if (format !== "markdown" && format !== "json") {
|
|
537
|
+
throw new Error("--format must be markdown or json.");
|
|
538
|
+
}
|
|
539
|
+
parsedArgs.format = format;
|
|
540
|
+
index += 1;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (arg === "--out-json") {
|
|
544
|
+
parsedArgs.outJson = requireValue(argv, index, arg);
|
|
545
|
+
index += 1;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (arg === "--out-md") {
|
|
549
|
+
parsedArgs.outMarkdown = requireValue(argv, index, arg);
|
|
550
|
+
index += 1;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (arg === "--bundle-id") {
|
|
554
|
+
parsedArgs.bundleId = requireValue(argv, index, arg);
|
|
555
|
+
index += 1;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (arg === "--generated-at") {
|
|
559
|
+
parsedArgs.generatedAt = requireValue(argv, index, arg);
|
|
560
|
+
index += 1;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
throw new Error(`Unknown argument "${arg}".`);
|
|
564
|
+
}
|
|
565
|
+
return parsedArgs;
|
|
566
|
+
}
|
|
567
|
+
function requireValue(argv, index, flag) {
|
|
568
|
+
const value = argv[index + 1];
|
|
569
|
+
if (!value || value.startsWith("--")) {
|
|
570
|
+
throw new Error(`${flag} requires a value.`);
|
|
571
|
+
}
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
async function loadCliConfig(configPath, context) {
|
|
575
|
+
const configExport = await loadCliConfigDefault(configPath, context.cwd);
|
|
576
|
+
if (typeof configExport === "function") {
|
|
577
|
+
return configExport(context);
|
|
578
|
+
}
|
|
579
|
+
return configExport;
|
|
580
|
+
}
|
|
581
|
+
function isCliSkip(value) {
|
|
582
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && value.skip === true && typeof value.reason === "string";
|
|
583
|
+
}
|
|
584
|
+
function assertCliConfig(value) {
|
|
585
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
586
|
+
throw new Error("Audit trail export CLI config must export an object.");
|
|
587
|
+
}
|
|
588
|
+
const config = value;
|
|
589
|
+
if (config.artifacts !== void 0 && !Array.isArray(config.artifacts)) {
|
|
590
|
+
throw new Error("Audit trail export CLI artifacts must be an array.");
|
|
591
|
+
}
|
|
592
|
+
if (config.artifactFiles !== void 0 && (!Array.isArray(config.artifactFiles) || config.artifactFiles.some((artifactFile) => typeof artifactFile !== "string"))) {
|
|
593
|
+
throw new Error("Audit trail export CLI artifactFiles must be an array of strings.");
|
|
594
|
+
}
|
|
595
|
+
if (config.bundle !== void 0) {
|
|
596
|
+
assertBundleConfig(config.bundle);
|
|
597
|
+
}
|
|
598
|
+
const outputs = config.outputs;
|
|
599
|
+
if (outputs !== void 0 && (!outputs || typeof outputs !== "object" || Array.isArray(outputs) || outputs.json !== void 0 && typeof outputs.json !== "string" || outputs.markdown !== void 0 && typeof outputs.markdown !== "string")) {
|
|
600
|
+
throw new Error("Audit trail export CLI outputs must contain string paths.");
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function assertBundleConfig(value) {
|
|
604
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
605
|
+
throw new Error("Audit trail export CLI bundle config must be an object.");
|
|
606
|
+
}
|
|
607
|
+
const bundle = value;
|
|
608
|
+
if (bundle.bundleId !== void 0 && typeof bundle.bundleId !== "string") {
|
|
609
|
+
throw new Error("Audit trail export CLI bundleId must be a string.");
|
|
610
|
+
}
|
|
611
|
+
if (bundle.generatedAt !== void 0 && typeof bundle.generatedAt !== "string") {
|
|
612
|
+
throw new Error("Audit trail export CLI generatedAt must be a string.");
|
|
613
|
+
}
|
|
614
|
+
if (bundle.forbiddenOutputValues !== void 0 && (!Array.isArray(bundle.forbiddenOutputValues) || bundle.forbiddenOutputValues.some((value2) => typeof value2 !== "string"))) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
"Audit trail export CLI forbiddenOutputValues must be an array of strings."
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
if (bundle.format !== void 0 && bundle.format !== "markdown" && bundle.format !== "json") {
|
|
620
|
+
throw new Error("Audit trail export CLI bundle format must be markdown or json.");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function loadArtifacts(options) {
|
|
624
|
+
const artifacts = [
|
|
625
|
+
...options.inlineArtifacts ?? []
|
|
626
|
+
];
|
|
627
|
+
for (const artifactFile of options.artifactFiles ?? []) {
|
|
628
|
+
const loadedArtifacts = await loadArtifactFile(options.cwd, artifactFile);
|
|
629
|
+
artifacts.push(...loadedArtifacts);
|
|
630
|
+
}
|
|
631
|
+
return artifacts;
|
|
632
|
+
}
|
|
633
|
+
async function loadArtifactFile(cwd, artifactFile) {
|
|
634
|
+
const artifactPath = resolve(cwd, artifactFile);
|
|
635
|
+
const body = await readFile(artifactPath, "utf8");
|
|
636
|
+
let parsed;
|
|
637
|
+
try {
|
|
638
|
+
parsed = JSON.parse(body);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
`Invalid audit artifact JSON in "${artifactFile}": ${error instanceof Error ? error.message : "parse failed"}`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
if (Array.isArray(parsed)) {
|
|
645
|
+
return parsed;
|
|
646
|
+
}
|
|
647
|
+
if (parsed && typeof parsed === "object") {
|
|
648
|
+
return [parsed];
|
|
649
|
+
}
|
|
650
|
+
throw new Error(
|
|
651
|
+
`Audit artifact file "${artifactFile}" must contain an artifact object or artifact array.`
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
async function writeArtifacts(options) {
|
|
655
|
+
const artifactPaths = {};
|
|
656
|
+
if (options.outputs.json) {
|
|
657
|
+
const outputPath = resolve(options.cwd, options.outputs.json);
|
|
658
|
+
await writeArtifact(
|
|
659
|
+
outputPath,
|
|
660
|
+
formatProbeMeshAuditTrailBundle(options.bundle, {
|
|
661
|
+
format: "json"
|
|
662
|
+
})
|
|
663
|
+
);
|
|
664
|
+
artifactPaths.json = outputPath;
|
|
665
|
+
}
|
|
666
|
+
if (options.outputs.markdown) {
|
|
667
|
+
const outputPath = resolve(options.cwd, options.outputs.markdown);
|
|
668
|
+
await writeArtifact(
|
|
669
|
+
outputPath,
|
|
670
|
+
formatProbeMeshAuditTrailBundle(options.bundle, {
|
|
671
|
+
format: "markdown"
|
|
672
|
+
})
|
|
673
|
+
);
|
|
674
|
+
artifactPaths.markdown = outputPath;
|
|
675
|
+
}
|
|
676
|
+
return artifactPaths;
|
|
677
|
+
}
|
|
678
|
+
async function writeArtifact(outputPath, body) {
|
|
679
|
+
await mkdir(dirname(outputPath), {
|
|
680
|
+
recursive: true
|
|
681
|
+
});
|
|
682
|
+
await writeFile(outputPath, body, "utf8");
|
|
683
|
+
}
|
|
684
|
+
function cliFailure(message) {
|
|
685
|
+
return {
|
|
686
|
+
exitCode: 2,
|
|
687
|
+
skipped: false,
|
|
688
|
+
stdout: "",
|
|
689
|
+
stderr: `${message}
|
|
690
|
+
${USAGE}
|
|
691
|
+
`
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
export {
|
|
696
|
+
PROBEMESH_AUDIT_TRAIL_BUNDLE_SCHEMA_VERSION,
|
|
697
|
+
createProbeMeshAuditTrailCollector,
|
|
698
|
+
createProbeMeshAuditTrailBundle,
|
|
699
|
+
summarizeProbeMeshAuditTrailBundle,
|
|
700
|
+
validateProbeMeshAuditTrailBundle,
|
|
701
|
+
assertProbeMeshAuditTrailBundle,
|
|
702
|
+
formatProbeMeshAuditTrailBundle,
|
|
703
|
+
runProbeMeshAuditTrailExportCli
|
|
704
|
+
};
|
|
705
|
+
//# sourceMappingURL=chunk-HNBDX7IU.js.map
|