@peac/cli 0.11.2 → 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 +190 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +191 -1
- package/dist/index.mjs.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;
|
|
@@ -2179,6 +2180,194 @@ JWKS for verification: ${path6.relative(outputDir, jwksPath)}`);
|
|
|
2179
2180
|
process.exitCode = 1;
|
|
2180
2181
|
}
|
|
2181
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
|
+
}
|
|
2182
2371
|
|
|
2183
2372
|
// src/index.ts
|
|
2184
2373
|
var program = new Command();
|
|
@@ -2314,6 +2503,7 @@ program.command("decode").description("Decode a PEAC receipt JWS (without verifi
|
|
|
2314
2503
|
program.addCommand(policy);
|
|
2315
2504
|
program.addCommand(conformance);
|
|
2316
2505
|
program.addCommand(samples);
|
|
2506
|
+
program.addCommand(reconcileCommand());
|
|
2317
2507
|
try {
|
|
2318
2508
|
program.parse();
|
|
2319
2509
|
} catch (err) {
|