@peac/cli 0.11.1 → 0.11.3
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/dist/commands/reconcile.d.ts +17 -0
- package/dist/commands/reconcile.d.ts.map +1 -0
- package/dist/index.cjs +284 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +285 -4
- package/dist/index.mjs.map +1 -1
- package/dist/lib/conformance/digest.d.ts.map +1 -1
- package/dist/lib/conformance/profiles.d.ts.map +1 -1
- package/dist/lib/conformance/validators.d.ts.map +1 -1
- package/dist/lib/conformance-runner.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/index.mjs
CHANGED
|
@@ -9,8 +9,9 @@ import { join } from 'path';
|
|
|
9
9
|
import { loadProfile, serializePolicyJson, serializePolicyYaml, ProfileError, createExamplePolicy, loadPolicy, PolicyValidationError, PolicyLoadError, compilePeacTxt, compileRobotsSnippet, compileAiprefTemplates, renderPolicyMarkdown, explainMatches, evaluate, listProfiles, getProfileSummary } from '@peac/policy-kit';
|
|
10
10
|
import { execSync } from 'child_process';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
|
-
import { validateWorkflowContextOrdered, isValidPurposeToken, isCanonicalPurpose, validateObligationsExtension, validateInteractionOrdered, validateDisputeAttestation, validateAttributionAttestation, validateAgentIdentityAttestation, ReceiptClaimsSchema, parseReceiptClaims } from '@peac/schema';
|
|
12
|
+
import { computeReceiptRef, validateWorkflowContextOrdered, isValidPurposeToken, isCanonicalPurpose, validateObligationsExtension, validateInteractionOrdered, validateDisputeAttestation, validateAttributionAttestation, validateAgentIdentityAttestation, ReceiptClaimsSchema, parseReceiptClaims } from '@peac/schema';
|
|
13
13
|
import { parse } from '@peac/disc';
|
|
14
|
+
import { readDisputeBundle } from '@peac/audit';
|
|
14
15
|
|
|
15
16
|
function getGlobalOptions(cmd) {
|
|
16
17
|
const parent = cmd.parent;
|
|
@@ -576,7 +577,8 @@ function computeCanonicalDigest(input) {
|
|
|
576
577
|
const canonical = canonicalize(input);
|
|
577
578
|
return { alg: "sha-256+jcs", value: sha256(canonical) };
|
|
578
579
|
} catch {
|
|
579
|
-
|
|
580
|
+
const serialized = JSON.stringify(input) ?? "null";
|
|
581
|
+
return { alg: "sha-256+json", value: sha256(serialized) };
|
|
580
582
|
}
|
|
581
583
|
}
|
|
582
584
|
var VECTORS_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".txt", ".jwks", ".pem", ".zip"]);
|
|
@@ -727,6 +729,18 @@ var PROFILE_CAPABILITIES = {
|
|
|
727
729
|
level: "shape",
|
|
728
730
|
validator: "structural-check",
|
|
729
731
|
notes: "Structure-only; no offer/receipt verification or term matching"
|
|
732
|
+
},
|
|
733
|
+
errors: {
|
|
734
|
+
profile: "errors.hints",
|
|
735
|
+
level: "semantic",
|
|
736
|
+
validator: "validateErrorHintsInput",
|
|
737
|
+
notes: "Validates next_action hint table shape and vocabulary (DD-132, DD-133)"
|
|
738
|
+
},
|
|
739
|
+
"content-usage": {
|
|
740
|
+
profile: "content-usage.parse",
|
|
741
|
+
level: "shape",
|
|
742
|
+
validator: "validateContentUsageInput",
|
|
743
|
+
notes: "Validates Content-Usage fixture input type; full parsing tested in dedicated suite"
|
|
730
744
|
}
|
|
731
745
|
};
|
|
732
746
|
function getCategoryCapability(category) {
|
|
@@ -1013,6 +1027,68 @@ function validateIssueInput(input) {
|
|
|
1013
1027
|
}
|
|
1014
1028
|
return { valid: true };
|
|
1015
1029
|
}
|
|
1030
|
+
var VALID_NEXT_ACTIONS = /* @__PURE__ */ new Set([
|
|
1031
|
+
"retry_after_delay",
|
|
1032
|
+
"retry_with_different_key",
|
|
1033
|
+
"retry_with_different_input",
|
|
1034
|
+
"refresh_attestation",
|
|
1035
|
+
"contact_issuer",
|
|
1036
|
+
"abort",
|
|
1037
|
+
"none"
|
|
1038
|
+
]);
|
|
1039
|
+
function validateErrorHintsInput(input) {
|
|
1040
|
+
if (typeof input !== "object" || input === null) {
|
|
1041
|
+
return {
|
|
1042
|
+
valid: false,
|
|
1043
|
+
error_code: "E_INVALID_ERROR_HINTS",
|
|
1044
|
+
error_message: "Error hints input must be an object"
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
const obj = input;
|
|
1048
|
+
if (!obj.hints || typeof obj.hints !== "object") {
|
|
1049
|
+
return {
|
|
1050
|
+
valid: false,
|
|
1051
|
+
error_code: "E_MISSING_HINTS",
|
|
1052
|
+
error_message: "Error hints missing required hints object"
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
const hints = obj.hints;
|
|
1056
|
+
for (const [code, hint] of Object.entries(hints)) {
|
|
1057
|
+
if (typeof hint !== "object" || hint === null) {
|
|
1058
|
+
return {
|
|
1059
|
+
valid: false,
|
|
1060
|
+
error_code: "E_INVALID_HINT",
|
|
1061
|
+
error_message: `Hint for ${code} must be an object`
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
const h = hint;
|
|
1065
|
+
if (!VALID_NEXT_ACTIONS.has(h.next_action)) {
|
|
1066
|
+
return {
|
|
1067
|
+
valid: false,
|
|
1068
|
+
error_code: "E_INVALID_NEXT_ACTION",
|
|
1069
|
+
error_message: `Hint for ${code} has invalid next_action: ${String(h.next_action)}`
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if (typeof h.retryable !== "boolean") {
|
|
1073
|
+
return {
|
|
1074
|
+
valid: false,
|
|
1075
|
+
error_code: "E_INVALID_RETRYABLE",
|
|
1076
|
+
error_message: `Hint for ${code} has non-boolean retryable: ${String(h.retryable)}`
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return { valid: true };
|
|
1081
|
+
}
|
|
1082
|
+
function validateContentUsageInput(input) {
|
|
1083
|
+
if (typeof input === "string") {
|
|
1084
|
+
return { valid: true };
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
valid: false,
|
|
1088
|
+
error_code: "E_INVALID_CONTENT_USAGE",
|
|
1089
|
+
error_message: "Content-Usage input must be a string (HTTP header value)"
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1016
1092
|
var CATEGORY_VALIDATORS = {
|
|
1017
1093
|
// Receipt claim validation
|
|
1018
1094
|
valid: (input) => {
|
|
@@ -1049,7 +1125,9 @@ var CATEGORY_VALIDATORS = {
|
|
|
1049
1125
|
return validateReceiptPayload(obj.payload ?? obj.claims ?? input);
|
|
1050
1126
|
},
|
|
1051
1127
|
workflow: validateWorkflowInput,
|
|
1052
|
-
x402: validateX402Input
|
|
1128
|
+
x402: validateX402Input,
|
|
1129
|
+
errors: validateErrorHintsInput,
|
|
1130
|
+
"content-usage": validateContentUsageInput
|
|
1053
1131
|
};
|
|
1054
1132
|
function getValidator(category) {
|
|
1055
1133
|
return CATEGORY_VALIDATORS[category] ?? ((input) => {
|
|
@@ -1148,8 +1226,22 @@ function runConformance(options, callbacks) {
|
|
|
1148
1226
|
const f = pack.fixtures[i];
|
|
1149
1227
|
const fixtureName = f.name ?? `fixture_${i}`;
|
|
1150
1228
|
const testId = `${baseTestId}.${fixtureName.replace(/\s+/g, "_")}`;
|
|
1151
|
-
const inputDigest = computeCanonicalDigest(f.input);
|
|
1152
1229
|
callbacks?.onTestStart?.(testId);
|
|
1230
|
+
if (!f.expected || typeof f.expected.valid !== "boolean") {
|
|
1231
|
+
skipped++;
|
|
1232
|
+
const result2 = {
|
|
1233
|
+
id: testId,
|
|
1234
|
+
category: profile,
|
|
1235
|
+
status: "skip",
|
|
1236
|
+
diagnostics: {
|
|
1237
|
+
skip_reason: "Non-standard fixture format (domain-specific test suite)"
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
results.push(result2);
|
|
1241
|
+
callbacks?.onTestComplete?.(result2);
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
const inputDigest = computeCanonicalDigest(f.input);
|
|
1153
1245
|
if (typeof f.input === "object" && f.input !== null) {
|
|
1154
1246
|
const inputObj = f.input;
|
|
1155
1247
|
if (inputObj.claims !== void 0 && inputObj.payload !== void 0) {
|
|
@@ -2088,6 +2180,194 @@ JWKS for verification: ${path6.relative(outputDir, jwksPath)}`);
|
|
|
2088
2180
|
process.exitCode = 1;
|
|
2089
2181
|
}
|
|
2090
2182
|
});
|
|
2183
|
+
var MAX_BUNDLE_SIZE = 16 * 1024 * 1024;
|
|
2184
|
+
function getGlobalOptions4(cmd) {
|
|
2185
|
+
const parent = cmd.parent;
|
|
2186
|
+
if (!parent) return {};
|
|
2187
|
+
return parent.opts();
|
|
2188
|
+
}
|
|
2189
|
+
function outputError4(error, details, opts) {
|
|
2190
|
+
if (opts.json) {
|
|
2191
|
+
console.log(JSON.stringify({ success: false, error, ...details }, null, 2));
|
|
2192
|
+
} else {
|
|
2193
|
+
console.error(`Error: ${error}`);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
function decodeJwsPayload(jws) {
|
|
2197
|
+
try {
|
|
2198
|
+
const { payload } = decode(jws);
|
|
2199
|
+
return payload;
|
|
2200
|
+
} catch {
|
|
2201
|
+
return null;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
function extractConflictKey(jws, receiptRef) {
|
|
2205
|
+
const payload = decodeJwsPayload(jws);
|
|
2206
|
+
if (!payload) {
|
|
2207
|
+
return `unknown|${receiptRef}`;
|
|
2208
|
+
}
|
|
2209
|
+
const iss = typeof payload.iss === "string" ? payload.iss : "unknown";
|
|
2210
|
+
if (typeof payload.jti === "string" && payload.jti.length > 0) {
|
|
2211
|
+
return `${iss}|${payload.jti}`;
|
|
2212
|
+
}
|
|
2213
|
+
if (typeof payload.rid === "string" && payload.rid.length > 0) {
|
|
2214
|
+
return `${iss}|${payload.rid}`;
|
|
2215
|
+
}
|
|
2216
|
+
return `${iss}|${receiptRef}`;
|
|
2217
|
+
}
|
|
2218
|
+
async function extractReceipts(contents, sourceLabel) {
|
|
2219
|
+
const records = [];
|
|
2220
|
+
for (const [_id, jws] of contents.receipts) {
|
|
2221
|
+
const receiptRef = await computeReceiptRef(jws);
|
|
2222
|
+
const key = extractConflictKey(jws, receiptRef);
|
|
2223
|
+
records.push({ key, jws, receipt_ref: receiptRef, source: sourceLabel });
|
|
2224
|
+
}
|
|
2225
|
+
return records;
|
|
2226
|
+
}
|
|
2227
|
+
function findDiffFields(jwsA, jwsB) {
|
|
2228
|
+
const payloadA = decodeJwsPayload(jwsA);
|
|
2229
|
+
const payloadB = decodeJwsPayload(jwsB);
|
|
2230
|
+
if (!payloadA || !payloadB) return ["payload_decode_failed"];
|
|
2231
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(payloadA), ...Object.keys(payloadB)]);
|
|
2232
|
+
const diffs = [];
|
|
2233
|
+
for (const k of allKeys) {
|
|
2234
|
+
if (JSON.stringify(payloadA[k]) !== JSON.stringify(payloadB[k])) {
|
|
2235
|
+
diffs.push(k);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
return diffs.sort();
|
|
2239
|
+
}
|
|
2240
|
+
async function readBundle(bundlePath, label) {
|
|
2241
|
+
if (!fs6.existsSync(bundlePath)) {
|
|
2242
|
+
return { ok: false, error: `Bundle file not found: ${bundlePath}` };
|
|
2243
|
+
}
|
|
2244
|
+
const stat = fs6.statSync(bundlePath);
|
|
2245
|
+
if (stat.size > MAX_BUNDLE_SIZE) {
|
|
2246
|
+
return { ok: false, error: `Bundle ${label} exceeds 16 MB size limit (${stat.size} bytes)` };
|
|
2247
|
+
}
|
|
2248
|
+
const resolved = fs6.realpathSync(bundlePath);
|
|
2249
|
+
if (resolved !== bundlePath && !resolved.startsWith(process.cwd())) {
|
|
2250
|
+
if (!fs6.existsSync(resolved)) {
|
|
2251
|
+
return { ok: false, error: `Path traversal detected for ${label}` };
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
const zipBuffer = fs6.readFileSync(bundlePath);
|
|
2255
|
+
const result = await readDisputeBundle(zipBuffer);
|
|
2256
|
+
if (!result.ok) {
|
|
2257
|
+
return { ok: false, error: `Failed to read ${label}: ${result.error.message}` };
|
|
2258
|
+
}
|
|
2259
|
+
return { ok: true, value: result.value };
|
|
2260
|
+
}
|
|
2261
|
+
function formatReportText2(report) {
|
|
2262
|
+
const lines = [
|
|
2263
|
+
"PEAC Reconciliation Report",
|
|
2264
|
+
"==========================",
|
|
2265
|
+
"",
|
|
2266
|
+
`Generated: ${report.generated_at}`,
|
|
2267
|
+
`Bundles: ${report.bundles.join(", ")}`,
|
|
2268
|
+
`Total receipts: ${report.total_receipts}`,
|
|
2269
|
+
`Merged receipts: ${report.merged_receipts}`,
|
|
2270
|
+
`Conflicts: ${report.conflicts.length}`,
|
|
2271
|
+
""
|
|
2272
|
+
];
|
|
2273
|
+
if (report.conflicts.length === 0) {
|
|
2274
|
+
lines.push("No conflicts detected. Bundles merge cleanly.");
|
|
2275
|
+
} else {
|
|
2276
|
+
lines.push("Conflicts:");
|
|
2277
|
+
lines.push("---------");
|
|
2278
|
+
for (const conflict of report.conflicts) {
|
|
2279
|
+
lines.push(` Key: ${conflict.key}`);
|
|
2280
|
+
lines.push(` Bundle A ref: ${conflict.bundle_a_receipt_ref}`);
|
|
2281
|
+
lines.push(` Bundle B ref: ${conflict.bundle_b_receipt_ref}`);
|
|
2282
|
+
lines.push(` Different fields: ${conflict.diff_fields.join(", ")}`);
|
|
2283
|
+
lines.push("");
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
return lines.join("\n");
|
|
2287
|
+
}
|
|
2288
|
+
function reconcileCommand() {
|
|
2289
|
+
const reconcile = new Command("reconcile").description(
|
|
2290
|
+
"Merge evidence bundles and detect conflicts (v0.11.3+)"
|
|
2291
|
+
);
|
|
2292
|
+
reconcile.argument("<bundle1>", "Path to first bundle ZIP file").argument("<bundle2>", "Path to second bundle ZIP file").option("-f, --format <format>", "Output format: json or text", "text").option("--fail-on-conflict", "Exit code 1 when any conflict detected", false).action(
|
|
2293
|
+
async (bundle1Path, bundle2Path, options, cmd) => {
|
|
2294
|
+
const globalOpts = getGlobalOptions4(cmd);
|
|
2295
|
+
const format = options.format || "text";
|
|
2296
|
+
const failOnConflict = !!options.failOnConflict;
|
|
2297
|
+
try {
|
|
2298
|
+
const [result1, result2] = await Promise.all([
|
|
2299
|
+
readBundle(bundle1Path, "bundle1"),
|
|
2300
|
+
readBundle(bundle2Path, "bundle2")
|
|
2301
|
+
]);
|
|
2302
|
+
if (!result1.ok) {
|
|
2303
|
+
outputError4(result1.error, {}, globalOpts);
|
|
2304
|
+
process.exitCode = 1;
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
if (!result2.ok) {
|
|
2308
|
+
outputError4(result2.error, {}, globalOpts);
|
|
2309
|
+
process.exitCode = 1;
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
const [receipts1, receipts2] = await Promise.all([
|
|
2313
|
+
extractReceipts(result1.value, bundle1Path),
|
|
2314
|
+
extractReceipts(result2.value, bundle2Path)
|
|
2315
|
+
]);
|
|
2316
|
+
const index = /* @__PURE__ */ new Map();
|
|
2317
|
+
for (const r of [...receipts1, ...receipts2]) {
|
|
2318
|
+
const existing = index.get(r.key) ?? [];
|
|
2319
|
+
existing.push(r);
|
|
2320
|
+
index.set(r.key, existing);
|
|
2321
|
+
}
|
|
2322
|
+
const conflicts = [];
|
|
2323
|
+
let mergedCount = 0;
|
|
2324
|
+
const sortedKeys = [...index.keys()].sort();
|
|
2325
|
+
for (const key of sortedKeys) {
|
|
2326
|
+
const records = index.get(key);
|
|
2327
|
+
const fromA = records.filter((r) => r.source === bundle1Path);
|
|
2328
|
+
const fromB = records.filter((r) => r.source === bundle2Path);
|
|
2329
|
+
if (fromA.length > 0 && fromB.length > 0) {
|
|
2330
|
+
const a = fromA[0];
|
|
2331
|
+
const b = fromB[0];
|
|
2332
|
+
if (a.receipt_ref !== b.receipt_ref) {
|
|
2333
|
+
conflicts.push({
|
|
2334
|
+
key,
|
|
2335
|
+
bundle_a_receipt_ref: a.receipt_ref,
|
|
2336
|
+
bundle_b_receipt_ref: b.receipt_ref,
|
|
2337
|
+
diff_fields: findDiffFields(a.jws, b.jws)
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
mergedCount++;
|
|
2341
|
+
} else {
|
|
2342
|
+
mergedCount++;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
const report = {
|
|
2346
|
+
version: "1.0",
|
|
2347
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2348
|
+
bundles: [bundle1Path, bundle2Path],
|
|
2349
|
+
total_receipts: receipts1.length + receipts2.length,
|
|
2350
|
+
merged_receipts: mergedCount,
|
|
2351
|
+
conflicts
|
|
2352
|
+
};
|
|
2353
|
+
if (format === "json" || globalOpts.json) {
|
|
2354
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2355
|
+
} else {
|
|
2356
|
+
console.log(formatReportText2(report));
|
|
2357
|
+
}
|
|
2358
|
+
if (failOnConflict && conflicts.length > 0) {
|
|
2359
|
+
process.exitCode = 1;
|
|
2360
|
+
} else {
|
|
2361
|
+
process.exitCode = 0;
|
|
2362
|
+
}
|
|
2363
|
+
} catch (err) {
|
|
2364
|
+
outputError4(err.message, {}, globalOpts);
|
|
2365
|
+
process.exitCode = 1;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
);
|
|
2369
|
+
return reconcile;
|
|
2370
|
+
}
|
|
2091
2371
|
|
|
2092
2372
|
// src/index.ts
|
|
2093
2373
|
var program = new Command();
|
|
@@ -2223,6 +2503,7 @@ program.command("decode").description("Decode a PEAC receipt JWS (without verifi
|
|
|
2223
2503
|
program.addCommand(policy);
|
|
2224
2504
|
program.addCommand(conformance);
|
|
2225
2505
|
program.addCommand(samples);
|
|
2506
|
+
program.addCommand(reconcileCommand());
|
|
2226
2507
|
try {
|
|
2227
2508
|
program.parse();
|
|
2228
2509
|
} catch (err) {
|