@peac/cli 0.10.9 → 0.10.10

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.
Files changed (60) hide show
  1. package/LICENSE +1 -1
  2. package/dist/index.cjs +2257 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.mjs +2236 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +21 -16
  7. package/dist/cli.js +0 -90
  8. package/dist/cli.js.map +0 -1
  9. package/dist/commands/bridge/index.js +0 -20
  10. package/dist/commands/bridge/index.js.map +0 -1
  11. package/dist/commands/bridge/install.js +0 -27
  12. package/dist/commands/bridge/install.js.map +0 -1
  13. package/dist/commands/bridge/start.js +0 -123
  14. package/dist/commands/bridge/start.js.map +0 -1
  15. package/dist/commands/bridge/status.js +0 -192
  16. package/dist/commands/bridge/status.js.map +0 -1
  17. package/dist/commands/bridge/stop.js +0 -130
  18. package/dist/commands/bridge/stop.js.map +0 -1
  19. package/dist/commands/bundle.js +0 -363
  20. package/dist/commands/bundle.js.map +0 -1
  21. package/dist/commands/conformance.js +0 -301
  22. package/dist/commands/conformance.js.map +0 -1
  23. package/dist/commands/discover.js +0 -39
  24. package/dist/commands/discover.js.map +0 -1
  25. package/dist/commands/hash.js +0 -106
  26. package/dist/commands/hash.js.map +0 -1
  27. package/dist/commands/policy.js +0 -712
  28. package/dist/commands/policy.js.map +0 -1
  29. package/dist/commands/samples.js +0 -360
  30. package/dist/commands/samples.js.map +0 -1
  31. package/dist/commands/verify.js +0 -50
  32. package/dist/commands/verify.js.map +0 -1
  33. package/dist/commands/workflow.d.ts +0 -11
  34. package/dist/commands/workflow.d.ts.map +0 -1
  35. package/dist/commands/workflow.js +0 -214
  36. package/dist/commands/workflow.js.map +0 -1
  37. package/dist/index.js +0 -240
  38. package/dist/index.js.map +0 -1
  39. package/dist/lib/conformance/digest.js +0 -202
  40. package/dist/lib/conformance/digest.js.map +0 -1
  41. package/dist/lib/conformance/index.js +0 -30
  42. package/dist/lib/conformance/index.js.map +0 -1
  43. package/dist/lib/conformance/manifest.js +0 -66
  44. package/dist/lib/conformance/manifest.js.map +0 -1
  45. package/dist/lib/conformance/profiles.js +0 -138
  46. package/dist/lib/conformance/profiles.js.map +0 -1
  47. package/dist/lib/conformance/types.js +0 -8
  48. package/dist/lib/conformance/types.js.map +0 -1
  49. package/dist/lib/conformance/validators.js +0 -415
  50. package/dist/lib/conformance/validators.js.map +0 -1
  51. package/dist/lib/conformance-runner.js +0 -433
  52. package/dist/lib/conformance-runner.js.map +0 -1
  53. package/dist/lib/samples-loader.js +0 -311
  54. package/dist/lib/samples-loader.js.map +0 -1
  55. package/dist/lib/version.js +0 -61
  56. package/dist/lib/version.js.map +0 -1
  57. package/dist/types.js +0 -6
  58. package/dist/types.js.map +0 -1
  59. package/dist/utils.js +0 -101
  60. package/dist/utils.js.map +0 -1
package/dist/index.mjs ADDED
@@ -0,0 +1,2236 @@
1
+ #!/usr/bin/env node
2
+ import { Command, CommanderError } from 'commander';
3
+ import { verifyReceipt, fetchIssuerConfig, parseIssuerConfig } from '@peac/protocol';
4
+ import { generateKeypair, base64urlEncode, sign, decode, canonicalize } from '@peac/crypto';
5
+ import * as fs6 from 'fs';
6
+ import { existsSync, readFileSync } from 'fs';
7
+ import * as path6 from 'path';
8
+ import { join } from 'path';
9
+ import { loadProfile, serializePolicyJson, serializePolicyYaml, ProfileError, createExamplePolicy, loadPolicy, PolicyValidationError, PolicyLoadError, compilePeacTxt, compileRobotsSnippet, compileAiprefTemplates, renderPolicyMarkdown, explainMatches, evaluate, listProfiles, getProfileSummary } from '@peac/policy-kit';
10
+ import { execSync } from 'child_process';
11
+ import { createHash } from 'crypto';
12
+ import { validateWorkflowContextOrdered, isValidPurposeToken, isCanonicalPurpose, validateObligationsExtension, validateInteractionOrdered, validateDisputeAttestation, validateAttributionAttestation, validateAgentIdentityAttestation, ReceiptClaimsSchema, parseReceiptClaims } from '@peac/schema';
13
+ import { parse } from '@peac/disc';
14
+
15
+ function getGlobalOptions(cmd) {
16
+ const parent = cmd.parent;
17
+ if (!parent) return {};
18
+ return parent.opts();
19
+ }
20
+ function output(data, opts, humanMessage) {
21
+ if (opts.json) {
22
+ console.log(JSON.stringify(data, null, 2));
23
+ }
24
+ }
25
+ function outputError(error, details, opts) {
26
+ if (opts.json) {
27
+ console.log(JSON.stringify({ success: false, error, ...details }, null, 2));
28
+ } else {
29
+ console.error(`Error: ${error}`);
30
+ }
31
+ }
32
+ var policy = new Command("policy").description("Policy file operations").option("--json", "Output in machine-readable JSON format").option("--yes", "Skip confirmation prompts (auto-confirm)").option("--strict", "Exit non-zero on warnings");
33
+ policy.command("init").description("Create a new PEAC policy file").option("-f, --format <format>", "Output format (yaml or json)", "yaml").option("-o, --output <file>", "Output file path").option("--force", "Overwrite existing file").option("--profile <id>", "Use a pre-built profile as template").action(
34
+ (options, cmd) => {
35
+ const globalOpts = getGlobalOptions(cmd);
36
+ try {
37
+ const format = options.format?.toLowerCase() || "yaml";
38
+ const outputPath = options.output || (format === "json" ? "peac-policy.json" : "peac-policy.yaml");
39
+ if (fs6.existsSync(outputPath) && !options.force && !globalOpts.yes) {
40
+ outputError(
41
+ `File already exists: ${outputPath}`,
42
+ { path: outputPath, hint: "Use --force or --yes to overwrite" },
43
+ globalOpts
44
+ );
45
+ process.exit(1);
46
+ }
47
+ let content;
48
+ let policyName;
49
+ if (options.profile) {
50
+ try {
51
+ const profile = loadProfile(options.profile);
52
+ const policyDoc = profile.policy;
53
+ content = format === "json" ? serializePolicyJson(policyDoc) : serializePolicyYaml(policyDoc);
54
+ policyName = profile.name;
55
+ } catch (err) {
56
+ if (err instanceof ProfileError) {
57
+ outputError(err.message, { code: err.code, profile: options.profile }, globalOpts);
58
+ process.exit(1);
59
+ }
60
+ throw err;
61
+ }
62
+ } else {
63
+ const example = createExamplePolicy();
64
+ content = format === "json" ? serializePolicyJson(example) : serializePolicyYaml(example);
65
+ policyName = "Example Policy";
66
+ }
67
+ fs6.writeFileSync(outputPath, content, "utf-8");
68
+ if (globalOpts.json) {
69
+ output(
70
+ {
71
+ success: true,
72
+ file: outputPath,
73
+ format,
74
+ profile: options.profile || null,
75
+ policyName
76
+ },
77
+ globalOpts
78
+ );
79
+ } else {
80
+ console.log(`Created policy file: ${outputPath}`);
81
+ if (options.profile) {
82
+ console.log(`Based on profile: ${options.profile}`);
83
+ }
84
+ console.log("");
85
+ console.log("Next steps:");
86
+ console.log(" 1. Edit the policy file to define your access rules");
87
+ console.log(" 2. Validate with: peac policy validate " + outputPath);
88
+ console.log(" 3. Generate artifacts with: peac policy generate " + outputPath);
89
+ }
90
+ process.exit(0);
91
+ } catch (err) {
92
+ outputError(err instanceof Error ? err.message : String(err), {}, globalOpts);
93
+ process.exit(1);
94
+ }
95
+ }
96
+ );
97
+ policy.command("validate").description("Validate a PEAC policy file (YAML or JSON)").argument("<file>", "Path to policy file").option("-v, --verbose", "Show detailed validation output").action((file, options, cmd) => {
98
+ const globalOpts = getGlobalOptions(cmd);
99
+ const warnings = [];
100
+ try {
101
+ const policyDoc = loadPolicy(file);
102
+ const result = {
103
+ success: true,
104
+ file,
105
+ valid: true,
106
+ warnings,
107
+ policy: {
108
+ version: policyDoc.version,
109
+ name: policyDoc.name || null,
110
+ defaultDecision: policyDoc.defaults.decision,
111
+ ruleCount: policyDoc.rules.length,
112
+ rules: options.verbose ? policyDoc.rules.map((r) => ({
113
+ name: r.name,
114
+ decision: r.decision,
115
+ subject: r.subject || null,
116
+ purpose: r.purpose || null,
117
+ licensingMode: r.licensing_mode || null
118
+ })) : void 0
119
+ }
120
+ };
121
+ if (globalOpts.json) {
122
+ output(result, globalOpts);
123
+ } else {
124
+ console.log(`Validating policy file: ${file}
125
+ `);
126
+ console.log("Policy is valid!\n");
127
+ console.log("Policy Information:");
128
+ console.log(` Version: ${policyDoc.version}`);
129
+ if (policyDoc.name) {
130
+ console.log(` Name: ${policyDoc.name}`);
131
+ }
132
+ console.log(` Default: ${policyDoc.defaults.decision}`);
133
+ console.log(` Rules: ${policyDoc.rules.length}`);
134
+ if (options.verbose && policyDoc.rules.length > 0) {
135
+ console.log("\nRules:");
136
+ for (const rule of policyDoc.rules) {
137
+ console.log(` - ${rule.name}: ${rule.decision}`);
138
+ if (rule.subject) {
139
+ const parts = [];
140
+ if (rule.subject.type) {
141
+ const types = Array.isArray(rule.subject.type) ? rule.subject.type.join("|") : rule.subject.type;
142
+ parts.push(`type=${types}`);
143
+ }
144
+ if (rule.subject.labels) {
145
+ parts.push(`labels=[${rule.subject.labels.join(",")}]`);
146
+ }
147
+ if (rule.subject.id) {
148
+ parts.push(`id=${rule.subject.id}`);
149
+ }
150
+ if (parts.length > 0) {
151
+ console.log(` subject: ${parts.join(", ")}`);
152
+ }
153
+ }
154
+ if (rule.purpose) {
155
+ const purposes = Array.isArray(rule.purpose) ? rule.purpose.join("|") : rule.purpose;
156
+ console.log(` purpose: ${purposes}`);
157
+ }
158
+ if (rule.licensing_mode) {
159
+ const modes = Array.isArray(rule.licensing_mode) ? rule.licensing_mode.join("|") : rule.licensing_mode;
160
+ console.log(` licensing_mode: ${modes}`);
161
+ }
162
+ }
163
+ }
164
+ if (warnings.length > 0) {
165
+ console.log("\nWarnings:");
166
+ for (const warning of warnings) {
167
+ console.log(` - ${warning}`);
168
+ }
169
+ }
170
+ }
171
+ if (globalOpts.strict && warnings.length > 0) {
172
+ process.exit(1);
173
+ }
174
+ process.exit(0);
175
+ } catch (err) {
176
+ if (err instanceof PolicyValidationError) {
177
+ const issues = err.issues.map((i) => ({
178
+ path: i.path.join("."),
179
+ message: i.message
180
+ }));
181
+ if (globalOpts.json) {
182
+ output(
183
+ {
184
+ success: false,
185
+ file,
186
+ valid: false,
187
+ error: "Validation failed",
188
+ issues
189
+ },
190
+ globalOpts
191
+ );
192
+ } else {
193
+ console.error("Policy validation failed:\n");
194
+ for (const issue of err.issues) {
195
+ console.error(` - ${issue.path.join(".")}: ${issue.message}`);
196
+ }
197
+ }
198
+ process.exit(1);
199
+ } else if (err instanceof PolicyLoadError) {
200
+ outputError(`Failed to load policy: ${err.message}`, { file }, globalOpts);
201
+ process.exit(1);
202
+ } else {
203
+ outputError(err instanceof Error ? err.message : String(err), {}, globalOpts);
204
+ process.exit(1);
205
+ }
206
+ }
207
+ });
208
+ policy.command("generate").description("Generate deployment artifacts from a policy file").argument("<file>", "Path to policy file").option("-o, --out <dir>", 'Output directory (use "-" for stdout)', "dist").option("--well-known", "Output peac.txt to .well-known/ subdirectory").option("--dry-run", "Show what would be generated without writing files").option("--peac-version <version>", "PEAC protocol version (default: 0.9)").option("--site-url <url>", "Site URL for peac.txt").option("--contact <email>", "Contact email for policy questions").option("--attribution <mode>", "Attribution requirement (required, optional, none)").option("--receipts <mode>", "Receipts requirement (required, optional, omit)").option("--rate-limit <limit>", 'Rate limit string (e.g., "100/hour", "unlimited")').option("--negotiate <url>", "Negotiate endpoint URL").option("--no-comments", "Omit comments from generated files").action(
209
+ (file, options, cmd) => {
210
+ const globalOpts = getGlobalOptions(cmd);
211
+ const isStdout = options.out === "-";
212
+ try {
213
+ if (!globalOpts.json && !isStdout) {
214
+ console.log(`Loading policy: ${file}
215
+ `);
216
+ }
217
+ const policyDoc = loadPolicy(file);
218
+ const compileOptions = {
219
+ peacVersion: options.peacVersion,
220
+ siteUrl: options.siteUrl,
221
+ contact: options.contact,
222
+ attribution: options.attribution,
223
+ receipts: options.receipts,
224
+ rateLimit: options.rateLimit,
225
+ negotiateUrl: options.negotiate,
226
+ includeComments: options.comments !== false
227
+ };
228
+ const peacTxt = compilePeacTxt(policyDoc, compileOptions);
229
+ const robotsTxt = compileRobotsSnippet(policyDoc, compileOptions);
230
+ const aiprefTemplates = compileAiprefTemplates(policyDoc, compileOptions);
231
+ const markdown = renderPolicyMarkdown(policyDoc, compileOptions);
232
+ if (isStdout || globalOpts.json) {
233
+ const result = {
234
+ success: true,
235
+ source: file,
236
+ artifacts: {
237
+ "peac.txt": peacTxt,
238
+ "robots-ai-snippet.txt": robotsTxt,
239
+ "aipref-headers.json": aiprefTemplates,
240
+ "ai-policy.md": markdown
241
+ }
242
+ };
243
+ if (isStdout && !globalOpts.json) {
244
+ console.log(peacTxt);
245
+ } else {
246
+ output(result, { json: true });
247
+ }
248
+ process.exit(0);
249
+ }
250
+ const outDir = options.out;
251
+ let peacTxtPath;
252
+ if (options.wellKnown) {
253
+ peacTxtPath = path6.join(outDir, ".well-known", "peac.txt");
254
+ } else {
255
+ peacTxtPath = path6.join(outDir, "peac.txt");
256
+ }
257
+ const robotsPath = path6.join(outDir, "robots-ai-snippet.txt");
258
+ const aiprefPath = path6.join(outDir, "aipref-headers.json");
259
+ const mdPath = path6.join(outDir, "ai-policy.md");
260
+ if (options.dryRun) {
261
+ console.log("Dry run - files that would be generated:\n");
262
+ console.log(`--- ${peacTxtPath} ---`);
263
+ console.log(peacTxt);
264
+ console.log(`--- ${robotsPath} ---`);
265
+ console.log(robotsTxt);
266
+ console.log(`--- ${aiprefPath} ---`);
267
+ console.log(JSON.stringify(aiprefTemplates, null, 2));
268
+ console.log(`
269
+ --- ${mdPath} ---`);
270
+ console.log(markdown);
271
+ process.exit(0);
272
+ }
273
+ if (!fs6.existsSync(outDir)) {
274
+ fs6.mkdirSync(outDir, { recursive: true });
275
+ }
276
+ if (options.wellKnown) {
277
+ const wellKnownDir = path6.join(outDir, ".well-known");
278
+ if (!fs6.existsSync(wellKnownDir)) {
279
+ fs6.mkdirSync(wellKnownDir, { recursive: true });
280
+ }
281
+ }
282
+ fs6.writeFileSync(peacTxtPath, peacTxt, "utf-8");
283
+ console.log(`Generated: ${peacTxtPath}`);
284
+ fs6.writeFileSync(robotsPath, robotsTxt, "utf-8");
285
+ console.log(`Generated: ${robotsPath}`);
286
+ fs6.writeFileSync(aiprefPath, JSON.stringify(aiprefTemplates, null, 2), "utf-8");
287
+ console.log(`Generated: ${aiprefPath}`);
288
+ fs6.writeFileSync(mdPath, markdown, "utf-8");
289
+ console.log(`Generated: ${mdPath}`);
290
+ console.log("");
291
+ console.log("Deployment instructions:");
292
+ if (options.wellKnown) {
293
+ console.log(` 1. Deploy ${peacTxtPath} to serve at /.well-known/peac.txt`);
294
+ } else {
295
+ console.log(` 1. Copy ${peacTxtPath} to /.well-known/peac.txt`);
296
+ }
297
+ console.log(` 2. Append ${robotsPath} to your robots.txt`);
298
+ console.log(` 3. Add headers from ${aiprefPath} to your server config`);
299
+ console.log(` 4. Publish ${mdPath} for human reference`);
300
+ process.exit(0);
301
+ } catch (err) {
302
+ if (err instanceof PolicyLoadError) {
303
+ outputError(`Failed to load policy: ${err.message}`, { file }, globalOpts);
304
+ process.exit(1);
305
+ } else {
306
+ outputError(err instanceof Error ? err.message : String(err), {}, globalOpts);
307
+ process.exit(1);
308
+ }
309
+ }
310
+ }
311
+ );
312
+ policy.command("explain").description("Explain which rule would apply for a given context").argument("<file>", "Path to policy file").option("-t, --type <type>", "Subject type (human, org, agent)").option("-l, --labels <labels>", "Subject labels (comma-separated)").option("-i, --id <id>", "Subject ID").option(
313
+ "-p, --purpose <purpose>",
314
+ "Access purpose (crawl, index, train, inference, ai_input, ai_index, search)"
315
+ ).option(
316
+ "-m, --licensing-mode <mode>",
317
+ "Licensing mode (subscription, pay_per_crawl, pay_per_inference)"
318
+ ).option("--all-matches", "Show all matching rules, not just the first").action(
319
+ (file, options, cmd) => {
320
+ const globalOpts = getGlobalOptions(cmd);
321
+ try {
322
+ const policyDoc = loadPolicy(file);
323
+ const context = {};
324
+ if (options.type || options.labels || options.id) {
325
+ context.subject = {};
326
+ if (options.type) {
327
+ context.subject.type = options.type;
328
+ }
329
+ if (options.labels) {
330
+ context.subject.labels = options.labels.split(",").map((l) => l.trim());
331
+ }
332
+ if (options.id) {
333
+ context.subject.id = options.id;
334
+ }
335
+ }
336
+ if (options.purpose) {
337
+ context.purpose = options.purpose;
338
+ }
339
+ if (options.licensingMode) {
340
+ context.licensing_mode = options.licensingMode;
341
+ }
342
+ if (options.allMatches) {
343
+ const matches = explainMatches(policyDoc, context);
344
+ if (globalOpts.json) {
345
+ output(
346
+ {
347
+ success: true,
348
+ context,
349
+ matches: matches.map((m) => {
350
+ if (m === "[default]") {
351
+ return {
352
+ rule: "[default]",
353
+ decision: policyDoc.defaults.decision
354
+ };
355
+ }
356
+ const rule = policyDoc.rules.find((r) => r.name === m);
357
+ return rule ? { rule: rule.name, decision: rule.decision } : { rule: m, decision: null };
358
+ })
359
+ },
360
+ globalOpts
361
+ );
362
+ } else {
363
+ console.log("Evaluation Context:");
364
+ if (context.subject) {
365
+ console.log(` Subject: ${JSON.stringify(context.subject)}`);
366
+ } else {
367
+ console.log(" Subject: (any)");
368
+ }
369
+ console.log(` Purpose: ${context.purpose || "(any)"}`);
370
+ console.log(` Licensing Mode: ${context.licensing_mode || "(any)"}`);
371
+ console.log();
372
+ console.log(`Matching Rules (${matches.length}):`);
373
+ for (const match of matches) {
374
+ if (match === "[default]") {
375
+ console.log(` - [default] -> ${policyDoc.defaults.decision}`);
376
+ } else {
377
+ const rule = policyDoc.rules.find((r) => r.name === match);
378
+ if (rule) {
379
+ console.log(` - ${rule.name} -> ${rule.decision}`);
380
+ }
381
+ }
382
+ }
383
+ }
384
+ } else {
385
+ const result = evaluate(policyDoc, context);
386
+ if (globalOpts.json) {
387
+ output(
388
+ {
389
+ success: true,
390
+ context,
391
+ result: {
392
+ decision: result.decision,
393
+ matchedRule: result.matched_rule || "[default]",
394
+ reason: result.reason || null
395
+ }
396
+ },
397
+ globalOpts
398
+ );
399
+ } else {
400
+ console.log("Evaluation Context:");
401
+ if (context.subject) {
402
+ console.log(` Subject: ${JSON.stringify(context.subject)}`);
403
+ } else {
404
+ console.log(" Subject: (any)");
405
+ }
406
+ console.log(` Purpose: ${context.purpose || "(any)"}`);
407
+ console.log(` Licensing Mode: ${context.licensing_mode || "(any)"}`);
408
+ console.log();
409
+ console.log("Result:");
410
+ console.log(` Decision: ${result.decision}`);
411
+ if (result.matched_rule) {
412
+ console.log(` Matched Rule: ${result.matched_rule}`);
413
+ } else {
414
+ console.log(" Matched Rule: (default)");
415
+ }
416
+ if (result.reason) {
417
+ console.log(` Reason: ${result.reason}`);
418
+ }
419
+ }
420
+ }
421
+ process.exit(0);
422
+ } catch (err) {
423
+ if (err instanceof PolicyLoadError) {
424
+ outputError(`Failed to load policy: ${err.message}`, { file }, globalOpts);
425
+ process.exit(1);
426
+ } else {
427
+ outputError(err instanceof Error ? err.message : String(err), {}, globalOpts);
428
+ process.exit(1);
429
+ }
430
+ }
431
+ }
432
+ );
433
+ policy.command("list-profiles").description("List available pre-built policy profiles").action((options, cmd) => {
434
+ const globalOpts = getGlobalOptions(cmd);
435
+ try {
436
+ const profileIds = listProfiles();
437
+ const profiles = profileIds.map((id) => getProfileSummary(id));
438
+ if (globalOpts.json) {
439
+ output(
440
+ {
441
+ success: true,
442
+ profiles: profiles.map((p) => ({
443
+ id: p.id,
444
+ name: p.name,
445
+ description: p.description,
446
+ defaultDecision: p.defaultDecision,
447
+ ruleCount: p.ruleCount,
448
+ requiresReceipt: p.requiresReceipt,
449
+ requiredParams: p.requiredParams,
450
+ optionalParams: p.optionalParams
451
+ }))
452
+ },
453
+ globalOpts
454
+ );
455
+ } else {
456
+ console.log("Available Policy Profiles:\n");
457
+ for (const profile of profiles) {
458
+ console.log(` ${profile.id}`);
459
+ console.log(` Name: ${profile.name}`);
460
+ console.log(` Default: ${profile.defaultDecision}`);
461
+ console.log(` Rules: ${profile.ruleCount}`);
462
+ if (profile.requiresReceipt) {
463
+ console.log(" Requires receipt: yes");
464
+ }
465
+ if (profile.requiredParams.length > 0) {
466
+ console.log(` Required params: ${profile.requiredParams.join(", ")}`);
467
+ }
468
+ console.log();
469
+ }
470
+ console.log('Use "peac policy show-profile <id>" for full details');
471
+ console.log('Use "peac policy init --profile <id>" to create a policy from a profile');
472
+ }
473
+ process.exit(0);
474
+ } catch (err) {
475
+ outputError(err instanceof Error ? err.message : String(err), {}, globalOpts);
476
+ process.exit(1);
477
+ }
478
+ });
479
+ policy.command("show-profile").description("Show details of a specific policy profile").argument("<id>", "Profile ID").action((id, options, cmd) => {
480
+ const globalOpts = getGlobalOptions(cmd);
481
+ try {
482
+ const profile = loadProfile(id);
483
+ const summary = getProfileSummary(id);
484
+ if (globalOpts.json) {
485
+ output(
486
+ {
487
+ success: true,
488
+ profile: {
489
+ id: profile.id,
490
+ name: profile.name,
491
+ description: profile.description,
492
+ policy: profile.policy,
493
+ parameters: profile.parameters,
494
+ defaults: profile.defaults
495
+ }
496
+ },
497
+ globalOpts
498
+ );
499
+ } else {
500
+ console.log(`Profile: ${profile.name}`);
501
+ console.log(`ID: ${profile.id}`);
502
+ console.log();
503
+ console.log("Description:");
504
+ console.log(
505
+ profile.description.split("\n").map((l) => ` ${l}`).join("\n")
506
+ );
507
+ console.log();
508
+ console.log("Policy:");
509
+ console.log(` Version: ${profile.policy.version}`);
510
+ console.log(` Default decision: ${profile.policy.defaults.decision}`);
511
+ console.log(` Rules: ${profile.policy.rules?.length || 0}`);
512
+ if (profile.policy.rules && profile.policy.rules.length > 0) {
513
+ console.log();
514
+ console.log("Rules:");
515
+ for (const rule of profile.policy.rules) {
516
+ console.log(` - ${rule.name}: ${rule.decision}`);
517
+ if (rule.purpose) {
518
+ const purposes = Array.isArray(rule.purpose) ? rule.purpose.join(", ") : rule.purpose;
519
+ console.log(` purpose: ${purposes}`);
520
+ }
521
+ if (rule.reason) {
522
+ console.log(` reason: ${rule.reason}`);
523
+ }
524
+ }
525
+ }
526
+ if (Object.keys(profile.parameters || {}).length > 0) {
527
+ console.log();
528
+ console.log("Parameters:");
529
+ for (const [key, param] of Object.entries(profile.parameters || {})) {
530
+ const required = param.required ? " (required)" : "";
531
+ console.log(` ${key}${required}`);
532
+ console.log(` ${param.description}`);
533
+ if (param.default) {
534
+ console.log(` Default: ${param.default}`);
535
+ }
536
+ if (param.example) {
537
+ console.log(` Example: ${param.example}`);
538
+ }
539
+ }
540
+ }
541
+ if (profile.defaults) {
542
+ console.log();
543
+ console.log("Defaults:");
544
+ if (profile.defaults.requirements?.receipt) {
545
+ console.log(" Receipt required: yes");
546
+ }
547
+ if (profile.defaults.rate_limit) {
548
+ console.log(
549
+ ` Rate limit: ${profile.defaults.rate_limit.limit}/${profile.defaults.rate_limit.window_seconds}s`
550
+ );
551
+ }
552
+ }
553
+ }
554
+ process.exit(0);
555
+ } catch (err) {
556
+ if (err instanceof ProfileError) {
557
+ outputError(err.message, { code: err.code, profile: id }, globalOpts);
558
+ process.exit(1);
559
+ }
560
+ outputError(err instanceof Error ? err.message : String(err), {}, globalOpts);
561
+ process.exit(1);
562
+ }
563
+ });
564
+ function sha256(content) {
565
+ return createHash("sha256").update(content, "utf8").digest("hex");
566
+ }
567
+ function zodPathToJsonPointer(path8) {
568
+ if (path8.length === 0) return "";
569
+ return "/" + path8.map((segment) => {
570
+ const str = String(segment);
571
+ return str.replace(/~/g, "~0").replace(/\//g, "~1");
572
+ }).join("/");
573
+ }
574
+ function computeCanonicalDigest(input) {
575
+ try {
576
+ const canonical = canonicalize(input);
577
+ return { alg: "sha-256+jcs", value: sha256(canonical) };
578
+ } catch {
579
+ return { alg: "sha-256+json", value: sha256(JSON.stringify(input)) };
580
+ }
581
+ }
582
+ var VECTORS_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".txt", ".jwks", ".pem", ".zip"]);
583
+ var VECTORS_EXCLUDE_FILES = /* @__PURE__ */ new Set([".DS_Store", ".gitkeep", "Thumbs.db"]);
584
+ function collectFilesRecursive(dir, basePath = "") {
585
+ const files = [];
586
+ if (!fs6.existsSync(dir)) return files;
587
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
588
+ for (const entry of entries) {
589
+ if (VECTORS_EXCLUDE_FILES.has(entry.name)) continue;
590
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
591
+ const fullPath = path6.join(dir, entry.name);
592
+ if (entry.isDirectory()) {
593
+ files.push(...collectFilesRecursive(fullPath, relativePath));
594
+ } else {
595
+ const ext = path6.extname(entry.name).toLowerCase();
596
+ if (VECTORS_FILE_EXTENSIONS.has(ext)) {
597
+ files.push({
598
+ relativePath,
599
+ content: fs6.readFileSync(fullPath, "utf8")
600
+ });
601
+ }
602
+ }
603
+ }
604
+ return files;
605
+ }
606
+ function computeVectorsDigest(fixturesDir, categories) {
607
+ const hash = createHash("sha256");
608
+ const allFiles = [];
609
+ const manifestPath = path6.join(fixturesDir, "manifest.json");
610
+ if (fs6.existsSync(manifestPath)) {
611
+ allFiles.push({
612
+ relativePath: "manifest.json",
613
+ content: fs6.readFileSync(manifestPath, "utf8")
614
+ });
615
+ }
616
+ const sortedCategories = [...categories].sort();
617
+ for (const cat of sortedCategories) {
618
+ const categoryPath = path6.join(fixturesDir, cat);
619
+ const categoryFiles = collectFilesRecursive(categoryPath, cat);
620
+ allFiles.push(...categoryFiles);
621
+ }
622
+ allFiles.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
623
+ for (const file of allFiles) {
624
+ const fileHash = sha256(file.content);
625
+ hash.update(file.relativePath);
626
+ hash.update("\0");
627
+ hash.update(fileHash);
628
+ }
629
+ return hash.digest("hex");
630
+ }
631
+ function loadManifest(fixturesDir) {
632
+ const manifestPath = path6.join(fixturesDir, "manifest.json");
633
+ if (!fs6.existsSync(manifestPath)) {
634
+ return {};
635
+ }
636
+ try {
637
+ return JSON.parse(fs6.readFileSync(manifestPath, "utf8"));
638
+ } catch {
639
+ return {};
640
+ }
641
+ }
642
+ function getManifestEntry(manifest, category, filename) {
643
+ return manifest[category]?.[filename];
644
+ }
645
+
646
+ // src/lib/conformance/profiles.ts
647
+ var PROFILE_CAPABILITIES = {
648
+ valid: {
649
+ profile: "receipt.verify.claims",
650
+ level: "semantic",
651
+ validator: "@peac/schema:ReceiptClaimsSchema"
652
+ },
653
+ invalid: {
654
+ profile: "receipt.verify.claims",
655
+ level: "semantic",
656
+ validator: "@peac/schema:ReceiptClaimsSchema"
657
+ },
658
+ edge: {
659
+ profile: "receipt.verify.claims",
660
+ level: "semantic",
661
+ validator: "@peac/schema:ReceiptClaimsSchema"
662
+ },
663
+ "agent-identity": {
664
+ profile: "receipt.verify.agent-identity",
665
+ level: "semantic",
666
+ validator: "@peac/schema:validateAgentIdentityAttestation"
667
+ },
668
+ attribution: {
669
+ profile: "receipt.verify.attribution",
670
+ level: "semantic",
671
+ validator: "@peac/schema:validateAttributionAttestation"
672
+ },
673
+ bundle: {
674
+ profile: "bundle.shape",
675
+ level: "shape",
676
+ validator: "structural-check",
677
+ notes: "Structure-only; no cryptographic integrity verification"
678
+ },
679
+ discovery: {
680
+ profile: "discovery.config",
681
+ level: "semantic",
682
+ validator: "@peac/disc:parse"
683
+ },
684
+ dispute: {
685
+ profile: "receipt.verify.dispute",
686
+ level: "semantic",
687
+ validator: "@peac/schema:validateDisputeAttestation"
688
+ },
689
+ interaction: {
690
+ profile: "receipt.verify.interaction",
691
+ level: "semantic",
692
+ validator: "@peac/schema:validateInteractionOrdered"
693
+ },
694
+ issue: {
695
+ profile: "receipt.issue",
696
+ level: "semantic",
697
+ validator: "@peac/schema:ReceiptClaimsSchema"
698
+ },
699
+ obligations: {
700
+ profile: "receipt.verify.obligations",
701
+ level: "semantic",
702
+ validator: "@peac/schema:validateObligationsExtension"
703
+ },
704
+ policy: {
705
+ profile: "policy.evaluate",
706
+ level: "shape",
707
+ validator: "structural-check",
708
+ notes: "Structure-only; no policy evaluation logic"
709
+ },
710
+ purpose: {
711
+ profile: "receipt.verify.purpose",
712
+ level: "semantic",
713
+ validator: "@peac/schema:isValidPurposeToken"
714
+ },
715
+ verifier: {
716
+ profile: "receipt.verify",
717
+ level: "semantic",
718
+ validator: "@peac/schema:ReceiptClaimsSchema"
719
+ },
720
+ workflow: {
721
+ profile: "workflow.validate",
722
+ level: "semantic",
723
+ validator: "@peac/schema:validateWorkflowContextOrdered"
724
+ },
725
+ x402: {
726
+ profile: "transport.x402.shape",
727
+ level: "shape",
728
+ validator: "structural-check",
729
+ notes: "Structure-only; no offer/receipt verification or term matching"
730
+ }
731
+ };
732
+ function getCategoryCapability(category) {
733
+ return PROFILE_CAPABILITIES[category] ?? {
734
+ profile: `receipt.verify.${category}`,
735
+ level: "semantic",
736
+ validator: "@peac/schema:ReceiptClaimsSchema (heuristic)",
737
+ notes: "Unknown category - uses semantic receipt validation when input looks like claims"
738
+ };
739
+ }
740
+ function getCategoryProfile(category) {
741
+ return getCategoryCapability(category).profile;
742
+ }
743
+ function shouldRunAtLevel(fixtureVersion, level) {
744
+ if (!fixtureVersion) return true;
745
+ const [major, minor] = fixtureVersion.split(".").map(Number);
746
+ const fixtureLevel = major > 0 || minor >= 10 ? "full" : minor >= 9 ? "standard" : "basic";
747
+ const levelOrder = ["basic", "standard", "full"];
748
+ return levelOrder.indexOf(fixtureLevel) <= levelOrder.indexOf(level);
749
+ }
750
+ function zodCodeToKeyword(code) {
751
+ const mapping = {
752
+ invalid_type: "type",
753
+ invalid_literal: "const",
754
+ unrecognized_keys: "additionalProperties",
755
+ invalid_union: "anyOf",
756
+ invalid_enum_value: "enum",
757
+ invalid_string: "format",
758
+ too_small: "minLength",
759
+ too_big: "maxLength",
760
+ custom: "custom"
761
+ };
762
+ return mapping[code] ?? code;
763
+ }
764
+ function validateReceiptPayload(payload) {
765
+ const result = ReceiptClaimsSchema.safeParse(payload);
766
+ if (result.success) {
767
+ return { valid: true };
768
+ }
769
+ const firstIssue = result.error.issues[0];
770
+ const zodPath = firstIssue?.path ?? [];
771
+ const errorPath = zodPathToJsonPointer(zodPath);
772
+ const errorKeyword = zodCodeToKeyword(firstIssue?.code ?? "unknown");
773
+ let errorCode = "E_INVALID_FORMAT";
774
+ const pathStr = zodPath.join(".");
775
+ if (pathStr === "iss" || pathStr.startsWith("iss.")) errorCode = "E_INVALID_ISSUER";
776
+ else if (pathStr === "aud" || pathStr.startsWith("aud.")) errorCode = "E_INVALID_AUDIENCE";
777
+ else if (pathStr === "iat") errorCode = "E_INVALID_IAT";
778
+ else if (pathStr === "exp") errorCode = "E_INVALID_EXP";
779
+ else if (pathStr === "rid") errorCode = "E_INVALID_RID";
780
+ else if (firstIssue?.code === "invalid_type" && zodPath.length > 0) {
781
+ errorCode = "E_REQUIRED";
782
+ }
783
+ return {
784
+ valid: false,
785
+ error_code: errorCode,
786
+ error_message: firstIssue?.message ?? "Unknown validation error",
787
+ error_path: errorPath,
788
+ error_keyword: errorKeyword
789
+ };
790
+ }
791
+ function validateParseReceiptClaims(payload) {
792
+ const pr = parseReceiptClaims(payload);
793
+ if (pr.ok) {
794
+ return { valid: true };
795
+ }
796
+ return {
797
+ valid: false,
798
+ error_code: pr.error.code,
799
+ error_message: pr.error.message
800
+ };
801
+ }
802
+ function validateDiscoveryInput(input) {
803
+ if (typeof input === "string") {
804
+ const result = parse(input);
805
+ if (result.valid) {
806
+ return { valid: true };
807
+ }
808
+ return {
809
+ valid: false,
810
+ error_code: "E_DISCOVERY_PARSE_ERROR",
811
+ error_message: result.errors?.join("; ") ?? "Discovery parse error"
812
+ };
813
+ }
814
+ if (typeof input === "object" && input !== null) {
815
+ const obj = input;
816
+ if (!obj.issuer || typeof obj.issuer !== "string") {
817
+ return {
818
+ valid: false,
819
+ error_code: "E_MISSING_ISSUER",
820
+ error_message: "Discovery document missing required issuer field"
821
+ };
822
+ }
823
+ if (!obj.jwks_uri || typeof obj.jwks_uri !== "string") {
824
+ return {
825
+ valid: false,
826
+ error_code: "E_MISSING_JWKS_URI",
827
+ error_message: "Discovery document missing required jwks_uri field"
828
+ };
829
+ }
830
+ return { valid: true };
831
+ }
832
+ return {
833
+ valid: false,
834
+ error_code: "E_INVALID_DISCOVERY",
835
+ error_message: "Discovery input must be string or object"
836
+ };
837
+ }
838
+ function validateAgentIdentityInput(input) {
839
+ const result = validateAgentIdentityAttestation(input);
840
+ if (result.ok) {
841
+ return { valid: true };
842
+ }
843
+ return {
844
+ valid: false,
845
+ error_code: "E_INVALID_AGENT_IDENTITY",
846
+ error_message: result.error
847
+ };
848
+ }
849
+ function validateAttributionInput(input) {
850
+ const result = validateAttributionAttestation(input);
851
+ if (result.ok) {
852
+ return { valid: true };
853
+ }
854
+ return {
855
+ valid: false,
856
+ error_code: "E_INVALID_ATTRIBUTION",
857
+ error_message: result.error
858
+ };
859
+ }
860
+ function validateDisputeInput(input) {
861
+ const result = validateDisputeAttestation(input);
862
+ if (result.ok) {
863
+ return { valid: true };
864
+ }
865
+ return {
866
+ valid: false,
867
+ error_code: "E_INVALID_DISPUTE",
868
+ error_message: result.error
869
+ };
870
+ }
871
+ function validateInteractionInput(input) {
872
+ const result = validateInteractionOrdered(input);
873
+ if (result.valid) {
874
+ return {
875
+ valid: true,
876
+ warnings: result.warnings.map((w) => w.message)
877
+ };
878
+ }
879
+ const firstError = result.errors[0];
880
+ return {
881
+ valid: false,
882
+ error_code: firstError?.code ?? "E_INVALID_INTERACTION",
883
+ error_message: firstError?.message ?? "Interaction validation failed"
884
+ };
885
+ }
886
+ function validateWorkflowInput(input) {
887
+ const result = validateWorkflowContextOrdered(input);
888
+ if (result.valid) {
889
+ return { valid: true };
890
+ }
891
+ return {
892
+ valid: false,
893
+ error_code: result.error_code,
894
+ error_message: result.error
895
+ };
896
+ }
897
+ function validateObligationsInput(input) {
898
+ const result = validateObligationsExtension(input);
899
+ if (result.ok) {
900
+ return { valid: true };
901
+ }
902
+ return {
903
+ valid: false,
904
+ error_code: "E_INVALID_OBLIGATIONS",
905
+ error_message: result.error
906
+ };
907
+ }
908
+ function validatePurposeInput(input) {
909
+ if (typeof input === "object" && input !== null) {
910
+ const obj = input;
911
+ if (obj.purpose_declared && Array.isArray(obj.purpose_declared)) {
912
+ for (const p of obj.purpose_declared) {
913
+ if (typeof p === "string" && !isValidPurposeToken(p) && !isCanonicalPurpose(p)) {
914
+ return {
915
+ valid: false,
916
+ error_code: "E_INVALID_PURPOSE_TOKEN",
917
+ error_message: `Invalid purpose token: ${p}`
918
+ };
919
+ }
920
+ }
921
+ }
922
+ if (obj.purpose_enforced && typeof obj.purpose_enforced === "string") {
923
+ if (!isValidPurposeToken(obj.purpose_enforced) && !isCanonicalPurpose(obj.purpose_enforced)) {
924
+ return {
925
+ valid: false,
926
+ error_code: "E_INVALID_PURPOSE_ENFORCED",
927
+ error_message: `Invalid enforced purpose: ${obj.purpose_enforced}`
928
+ };
929
+ }
930
+ }
931
+ return { valid: true };
932
+ }
933
+ return {
934
+ valid: false,
935
+ error_code: "E_INVALID_PURPOSE",
936
+ error_message: "Purpose input must be object with purpose_declared/purpose_enforced"
937
+ };
938
+ }
939
+ function validateBundleInput(input) {
940
+ if (typeof input !== "object" || input === null) {
941
+ return {
942
+ valid: false,
943
+ error_code: "E_INVALID_BUNDLE",
944
+ error_message: "Bundle must be an object"
945
+ };
946
+ }
947
+ const obj = input;
948
+ if (!obj.version || typeof obj.version !== "string") {
949
+ return {
950
+ valid: false,
951
+ error_code: "E_MISSING_VERSION",
952
+ error_message: "Bundle missing required version field"
953
+ };
954
+ }
955
+ if (!Array.isArray(obj.entries) && !Array.isArray(obj.receipts)) {
956
+ return {
957
+ valid: false,
958
+ error_code: "E_MISSING_ENTRIES",
959
+ error_message: "Bundle missing entries or receipts array"
960
+ };
961
+ }
962
+ return { valid: true };
963
+ }
964
+ function validateX402Input(input) {
965
+ if (typeof input !== "object" || input === null) {
966
+ return {
967
+ valid: false,
968
+ error_code: "E_INVALID_X402",
969
+ error_message: "x402 input must be an object"
970
+ };
971
+ }
972
+ const obj = input;
973
+ if (obj.accepts && Array.isArray(obj.accepts)) {
974
+ for (const accept of obj.accepts) {
975
+ if (typeof accept !== "object" || accept === null) {
976
+ return {
977
+ valid: false,
978
+ error_code: "E_INVALID_ACCEPTS",
979
+ error_message: "x402 accepts must contain payment option objects"
980
+ };
981
+ }
982
+ }
983
+ return { valid: true };
984
+ }
985
+ if (obj.rail && obj.reference) {
986
+ return { valid: true };
987
+ }
988
+ return {
989
+ valid: false,
990
+ error_code: "E_INVALID_X402_STRUCTURE",
991
+ error_message: "x402 input missing required fields (accepts or rail+reference)"
992
+ };
993
+ }
994
+ function validatePolicyInput(input) {
995
+ if (typeof input !== "object" || input === null) {
996
+ return {
997
+ valid: false,
998
+ error_code: "E_INVALID_POLICY",
999
+ error_message: "Policy input must be an object"
1000
+ };
1001
+ }
1002
+ return { valid: true };
1003
+ }
1004
+ function validateIssueInput(input) {
1005
+ if (typeof input === "object" && input !== null) {
1006
+ const obj = input;
1007
+ if (obj.claims) {
1008
+ return validateReceiptPayload(obj.claims);
1009
+ }
1010
+ if (obj.payload) {
1011
+ return validateReceiptPayload(obj.payload);
1012
+ }
1013
+ }
1014
+ return { valid: true };
1015
+ }
1016
+ var CATEGORY_VALIDATORS = {
1017
+ // Receipt claim validation
1018
+ valid: (input) => {
1019
+ const obj = input;
1020
+ return validateReceiptPayload(obj.payload ?? obj.claims ?? input);
1021
+ },
1022
+ invalid: (input) => {
1023
+ const obj = input;
1024
+ return validateReceiptPayload(obj.payload ?? obj.claims ?? input);
1025
+ },
1026
+ edge: (input) => {
1027
+ const obj = input;
1028
+ return validateReceiptPayload(obj.payload ?? obj.claims ?? input);
1029
+ },
1030
+ // Parse-level validation (unified parser, parse-level error codes).
1031
+ // The claims/payload ambiguity guard lives in the runner (test-harness concern).
1032
+ parse: (input) => {
1033
+ const obj = input;
1034
+ return validateParseReceiptClaims(obj.claims ?? obj.payload ?? input);
1035
+ },
1036
+ // Category-specific validators
1037
+ "agent-identity": validateAgentIdentityInput,
1038
+ attribution: validateAttributionInput,
1039
+ bundle: validateBundleInput,
1040
+ discovery: validateDiscoveryInput,
1041
+ dispute: validateDisputeInput,
1042
+ interaction: validateInteractionInput,
1043
+ issue: validateIssueInput,
1044
+ obligations: validateObligationsInput,
1045
+ policy: validatePolicyInput,
1046
+ purpose: validatePurposeInput,
1047
+ verifier: (input) => {
1048
+ const obj = input;
1049
+ return validateReceiptPayload(obj.payload ?? obj.claims ?? input);
1050
+ },
1051
+ workflow: validateWorkflowInput,
1052
+ x402: validateX402Input
1053
+ };
1054
+ function getValidator(category) {
1055
+ return CATEGORY_VALIDATORS[category] ?? ((input) => {
1056
+ if (typeof input === "object" && input !== null) {
1057
+ const obj = input;
1058
+ if (obj.payload || obj.claims) {
1059
+ return validateReceiptPayload(obj.payload ?? obj.claims);
1060
+ }
1061
+ if ("iss" in obj || "aud" in obj || "iat" in obj || "rid" in obj) {
1062
+ return validateReceiptPayload(input);
1063
+ }
1064
+ }
1065
+ return { valid: true };
1066
+ });
1067
+ }
1068
+
1069
+ // src/lib/conformance-runner.ts
1070
+ function getGitSha() {
1071
+ try {
1072
+ return execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
1073
+ } catch {
1074
+ return void 0;
1075
+ }
1076
+ }
1077
+ function runConformance(options, callbacks) {
1078
+ const startTime = Date.now();
1079
+ const { fixturesDir, level, category, implementationName, implementationVersion } = options;
1080
+ const manifest = loadManifest(fixturesDir);
1081
+ const entries = fs6.readdirSync(fixturesDir, { withFileTypes: true });
1082
+ let categories = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
1083
+ if (category) {
1084
+ if (!categories.includes(category)) {
1085
+ throw new Error(`Category not found: ${category}`);
1086
+ }
1087
+ categories = [category];
1088
+ }
1089
+ const vectorsDigest = computeVectorsDigest(fixturesDir, categories);
1090
+ const profiles = /* @__PURE__ */ new Set();
1091
+ const results = [];
1092
+ let passed = 0;
1093
+ let failed = 0;
1094
+ let skipped = 0;
1095
+ for (const cat of categories) {
1096
+ const categoryPath = path6.join(fixturesDir, cat);
1097
+ const profile = getCategoryProfile(cat);
1098
+ profiles.add(profile);
1099
+ const validator = getValidator(cat);
1100
+ const files = fs6.readdirSync(categoryPath).filter((f) => f.endsWith(".json")).sort();
1101
+ for (const file of files) {
1102
+ const filePath = path6.join(categoryPath, file);
1103
+ const content = fs6.readFileSync(filePath, "utf8");
1104
+ let fixture;
1105
+ const baseTestId = `${cat}.${file.replace(".json", "")}`;
1106
+ callbacks?.onTestStart?.(baseTestId);
1107
+ try {
1108
+ fixture = JSON.parse(content);
1109
+ } catch (err) {
1110
+ failed++;
1111
+ const result = {
1112
+ id: `${baseTestId}.parse`,
1113
+ category: profile,
1114
+ status: "fail",
1115
+ observed: {
1116
+ valid: false,
1117
+ error_code: "E_INVALID_JSON",
1118
+ error_message: err.message
1119
+ },
1120
+ diagnostics: {
1121
+ error_code: "E_INVALID_JSON",
1122
+ error_message: err.message
1123
+ }
1124
+ };
1125
+ results.push(result);
1126
+ callbacks?.onTestComplete?.(result);
1127
+ continue;
1128
+ }
1129
+ const fixtureObj = fixture;
1130
+ const isFixturePack = Array.isArray(fixtureObj.fixtures);
1131
+ if (isFixturePack) {
1132
+ const pack = fixture;
1133
+ if (!shouldRunAtLevel(pack.version, level)) {
1134
+ skipped++;
1135
+ const result = {
1136
+ id: baseTestId,
1137
+ category: profile,
1138
+ status: "skip",
1139
+ diagnostics: {
1140
+ skip_reason: `Fixture version ${pack.version} exceeds level ${level}`
1141
+ }
1142
+ };
1143
+ results.push(result);
1144
+ callbacks?.onTestComplete?.(result);
1145
+ continue;
1146
+ }
1147
+ for (let i = 0; i < pack.fixtures.length; i++) {
1148
+ const f = pack.fixtures[i];
1149
+ const fixtureName = f.name ?? `fixture_${i}`;
1150
+ const testId = `${baseTestId}.${fixtureName.replace(/\s+/g, "_")}`;
1151
+ const inputDigest = computeCanonicalDigest(f.input);
1152
+ callbacks?.onTestStart?.(testId);
1153
+ if (typeof f.input === "object" && f.input !== null) {
1154
+ const inputObj = f.input;
1155
+ if (inputObj.claims !== void 0 && inputObj.payload !== void 0) {
1156
+ failed++;
1157
+ const result2 = {
1158
+ id: testId,
1159
+ category: profile,
1160
+ status: "fail",
1161
+ diagnostics: {
1162
+ error_message: 'Fixture has both "claims" and "payload" -- ambiguous; use one'
1163
+ }
1164
+ };
1165
+ results.push(result2);
1166
+ callbacks?.onTestComplete?.(result2);
1167
+ continue;
1168
+ }
1169
+ }
1170
+ const observed = validator(f.input);
1171
+ const expectedValid = f.expected.valid;
1172
+ const actualValid = observed.valid;
1173
+ const expectedError = f.expected.error_code ?? f.expected.error;
1174
+ const actualError = observed.error_code;
1175
+ let status;
1176
+ if (expectedValid && actualValid) {
1177
+ status = "pass";
1178
+ passed++;
1179
+ } else if (!expectedValid && !actualValid) {
1180
+ if (expectedError && actualError && expectedError !== actualError) {
1181
+ status = "fail";
1182
+ failed++;
1183
+ } else {
1184
+ status = "pass";
1185
+ passed++;
1186
+ }
1187
+ } else {
1188
+ status = "fail";
1189
+ failed++;
1190
+ }
1191
+ const result = {
1192
+ id: testId,
1193
+ category: profile,
1194
+ status,
1195
+ expected: {
1196
+ valid: expectedValid,
1197
+ error_code: expectedError
1198
+ },
1199
+ observed: {
1200
+ valid: actualValid,
1201
+ error_code: actualError,
1202
+ error_message: observed.error_message
1203
+ },
1204
+ diagnostics: {
1205
+ input_digest: inputDigest,
1206
+ warnings: observed.warnings
1207
+ }
1208
+ };
1209
+ results.push(result);
1210
+ callbacks?.onTestComplete?.(result);
1211
+ }
1212
+ } else {
1213
+ const singleFixture = fixture;
1214
+ const testId = baseTestId;
1215
+ const manifestEntry = getManifestEntry(manifest, cat, file);
1216
+ const inputToValidate = singleFixture.payload ?? fixture;
1217
+ const inputDigest = singleFixture.payload ? computeCanonicalDigest(inputToValidate) : { alg: "sha-256", value: sha256(content) };
1218
+ const expectedValid = singleFixture.expected_valid ?? manifestEntry?.expected_valid ?? (cat === "valid" || cat === "edge");
1219
+ const expectedError = singleFixture.expected_error ?? manifestEntry?.expected_error_code;
1220
+ const expectedPath = manifestEntry?.expected_path;
1221
+ const expectedKeyword = manifestEntry?.expected_keyword;
1222
+ const observed = validator(inputToValidate);
1223
+ let status;
1224
+ if (expectedValid && observed.valid) {
1225
+ status = "pass";
1226
+ passed++;
1227
+ } else if (!expectedValid && !observed.valid) {
1228
+ let mismatch = false;
1229
+ if (expectedError && observed.error_code && expectedError !== observed.error_code) {
1230
+ mismatch = true;
1231
+ }
1232
+ if (expectedPath && observed.error_path && expectedPath !== observed.error_path) {
1233
+ mismatch = true;
1234
+ }
1235
+ if (expectedKeyword && observed.error_keyword && expectedKeyword !== observed.error_keyword) {
1236
+ mismatch = true;
1237
+ }
1238
+ if (mismatch) {
1239
+ status = "fail";
1240
+ failed++;
1241
+ } else {
1242
+ status = "pass";
1243
+ passed++;
1244
+ }
1245
+ } else {
1246
+ status = "fail";
1247
+ failed++;
1248
+ }
1249
+ const result = {
1250
+ id: testId,
1251
+ category: profile,
1252
+ status,
1253
+ expected: {
1254
+ valid: expectedValid,
1255
+ error_code: expectedError,
1256
+ error_path: expectedPath,
1257
+ error_keyword: expectedKeyword
1258
+ },
1259
+ observed: {
1260
+ valid: observed.valid,
1261
+ error_code: observed.error_code,
1262
+ error_path: observed.error_path,
1263
+ error_keyword: observed.error_keyword,
1264
+ error_message: observed.error_message
1265
+ },
1266
+ diagnostics: {
1267
+ input_digest: inputDigest,
1268
+ warnings: observed.warnings
1269
+ }
1270
+ };
1271
+ results.push(result);
1272
+ callbacks?.onTestComplete?.(result);
1273
+ }
1274
+ }
1275
+ }
1276
+ const capabilities = [];
1277
+ for (const cat of categories) {
1278
+ const capability = getCategoryCapability(cat);
1279
+ if (!capabilities.some((c) => c.profile === capability.profile)) {
1280
+ capabilities.push(capability);
1281
+ }
1282
+ }
1283
+ capabilities.sort((a, b) => a.profile.localeCompare(b.profile));
1284
+ const endTime = Date.now();
1285
+ const report = {
1286
+ report_version: "peac-conformance-report/0.1",
1287
+ suite: {
1288
+ name: "peac-core-conformance",
1289
+ version: "0.1.0",
1290
+ vectors_digest: {
1291
+ alg: "sha-256",
1292
+ value: vectorsDigest
1293
+ },
1294
+ profiles: Array.from(profiles).sort(),
1295
+ capabilities
1296
+ },
1297
+ implementation: {
1298
+ name: implementationName ?? "@peac/cli",
1299
+ version: implementationVersion ?? "0.0.0"
1300
+ },
1301
+ summary: {
1302
+ passed,
1303
+ failed,
1304
+ skipped,
1305
+ total: passed + failed + skipped,
1306
+ status: failed === 0 ? "pass" : "fail"
1307
+ },
1308
+ results,
1309
+ meta: {
1310
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
1311
+ duration_ms: endTime - startTime,
1312
+ runner: {
1313
+ name: "@peac/cli",
1314
+ version: implementationVersion ?? "0.0.0",
1315
+ git_sha: getGitSha()
1316
+ }
1317
+ }
1318
+ };
1319
+ return report;
1320
+ }
1321
+ function formatReportText(report) {
1322
+ const lines = [];
1323
+ lines.push("PEAC Conformance Test Results");
1324
+ lines.push("=============================");
1325
+ lines.push("");
1326
+ lines.push(`Status: ${report.summary.status.toUpperCase()}`);
1327
+ lines.push(`Profiles: ${report.suite.profiles.join(", ")}`);
1328
+ lines.push("");
1329
+ lines.push("Summary:");
1330
+ lines.push(` Passed: ${report.summary.passed}`);
1331
+ lines.push(` Failed: ${report.summary.failed}`);
1332
+ lines.push(` Skipped: ${report.summary.skipped}`);
1333
+ lines.push(` Total: ${report.summary.total}`);
1334
+ const failures = report.results.filter((r) => r.status === "fail");
1335
+ if (failures.length > 0) {
1336
+ lines.push("");
1337
+ lines.push("Failures:");
1338
+ for (const f of failures) {
1339
+ lines.push(` - ${f.id}`);
1340
+ if (f.expected) {
1341
+ lines.push(
1342
+ ` Expected: valid=${f.expected.valid}${f.expected.error_code ? `, error=${f.expected.error_code}` : ""}`
1343
+ );
1344
+ }
1345
+ if (f.observed) {
1346
+ lines.push(
1347
+ ` Observed: valid=${f.observed.valid}${f.observed.error_code ? `, error=${f.observed.error_code}` : ""}`
1348
+ );
1349
+ }
1350
+ }
1351
+ }
1352
+ return lines.join("\n");
1353
+ }
1354
+ function formatReportMarkdown(report) {
1355
+ const lines = [];
1356
+ lines.push("# PEAC Conformance Report");
1357
+ lines.push("");
1358
+ lines.push(`**Status:** ${report.summary.status.toUpperCase()}`);
1359
+ lines.push(`**Profiles:** ${report.suite.profiles.join(", ")}`);
1360
+ lines.push("");
1361
+ lines.push("## Summary");
1362
+ lines.push("");
1363
+ lines.push("| Metric | Count |");
1364
+ lines.push("|--------|-------|");
1365
+ lines.push(`| Passed | ${report.summary.passed} |`);
1366
+ lines.push(`| Failed | ${report.summary.failed} |`);
1367
+ lines.push(`| Skipped | ${report.summary.skipped} |`);
1368
+ lines.push(`| Total | ${report.summary.total} |`);
1369
+ const failures = report.results.filter((r) => r.status === "fail");
1370
+ if (failures.length > 0) {
1371
+ lines.push("");
1372
+ lines.push("## Failures");
1373
+ lines.push("");
1374
+ for (const f of failures) {
1375
+ lines.push(`### ${f.id}`);
1376
+ lines.push("");
1377
+ if (f.expected) {
1378
+ lines.push(
1379
+ `- **Expected:** valid=${f.expected.valid}${f.expected.error_code ? `, error=${f.expected.error_code}` : ""}`
1380
+ );
1381
+ }
1382
+ if (f.observed) {
1383
+ lines.push(
1384
+ `- **Observed:** valid=${f.observed.valid}${f.observed.error_code ? `, error=${f.observed.error_code}` : ""}`
1385
+ );
1386
+ }
1387
+ lines.push("");
1388
+ }
1389
+ }
1390
+ return lines.join("\n");
1391
+ }
1392
+ function getVersion() {
1393
+ try {
1394
+ const pkgPath = join(__dirname, "..", "..", "package.json");
1395
+ if (existsSync(pkgPath)) {
1396
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
1397
+ if (pkg.version) return pkg.version;
1398
+ }
1399
+ } catch {
1400
+ }
1401
+ try {
1402
+ const pkgPath = join(process.cwd(), "packages", "cli", "package.json");
1403
+ if (existsSync(pkgPath)) {
1404
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
1405
+ if (pkg.version) return pkg.version;
1406
+ }
1407
+ } catch {
1408
+ }
1409
+ return "0.0.0-unknown";
1410
+ }
1411
+ function getRuntime() {
1412
+ return `node-${process.version.slice(1)}`;
1413
+ }
1414
+ function getCommit() {
1415
+ return process.env.GIT_COMMIT ?? process.env.GITHUB_SHA;
1416
+ }
1417
+
1418
+ // src/commands/conformance.ts
1419
+ function getGlobalOptions2(cmd) {
1420
+ const parent = cmd.parent;
1421
+ if (!parent) return {};
1422
+ return parent.opts();
1423
+ }
1424
+ function outputError2(error, details, opts) {
1425
+ if (opts.json) {
1426
+ console.log(JSON.stringify({ success: false, error, ...details }, null, 2));
1427
+ } else {
1428
+ console.error(`Error: ${error}`);
1429
+ if (details.code) {
1430
+ console.error(`Code: ${details.code}`);
1431
+ }
1432
+ if (details.hint) {
1433
+ console.error(`Hint: ${details.hint}`);
1434
+ }
1435
+ }
1436
+ }
1437
+ function findFixturesDir(customPath) {
1438
+ if (customPath) {
1439
+ if (fs6.existsSync(customPath)) {
1440
+ return customPath;
1441
+ }
1442
+ return null;
1443
+ }
1444
+ const cliPath = path6.resolve(__dirname, "../../../../specs/conformance/fixtures");
1445
+ if (fs6.existsSync(cliPath)) {
1446
+ return cliPath;
1447
+ }
1448
+ const repoPath = path6.resolve(process.cwd(), "specs/conformance/fixtures");
1449
+ if (fs6.existsSync(repoPath)) {
1450
+ return repoPath;
1451
+ }
1452
+ const upPath = path6.resolve(process.cwd(), "../../specs/conformance/fixtures");
1453
+ if (fs6.existsSync(upPath)) {
1454
+ return upPath;
1455
+ }
1456
+ return null;
1457
+ }
1458
+ function listCategories(fixturesDir) {
1459
+ const entries = fs6.readdirSync(fixturesDir, { withFileTypes: true });
1460
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
1461
+ }
1462
+ function countFixtures(categoryPath) {
1463
+ if (!fs6.existsSync(categoryPath)) return 0;
1464
+ const files = fs6.readdirSync(categoryPath);
1465
+ return files.filter((f) => f.endsWith(".json")).length;
1466
+ }
1467
+ var conformance = new Command("conformance").description("PEAC conformance testing (v0.10.8+)").option("--json", "Output in JSON format");
1468
+ conformance.command("run").description("Run conformance tests").option("-l, --level <level>", "Conformance level: basic, standard, full", "standard").option("-c, --category <category>", "Test specific category only").option("-o, --output <format>", "Output format: json, text, markdown", "text").option("-v, --verbose", "Show detailed test output").option("--fixtures <path>", "Path to fixtures directory").option("--implementation <name>", "Implementation name for report", "@peac/cli").action(async (options, cmd) => {
1469
+ const globalOpts = getGlobalOptions2(cmd);
1470
+ const startTime = Date.now();
1471
+ try {
1472
+ const fixturesDir = findFixturesDir(options.fixtures);
1473
+ if (!fixturesDir) {
1474
+ outputError2(
1475
+ "Fixtures directory not found",
1476
+ {
1477
+ hint: options.fixtures ? `Specified path does not exist: ${options.fixtures}` : "Run from repo root, specify --fixtures <path>, or ensure specs/conformance/fixtures is available"
1478
+ },
1479
+ globalOpts
1480
+ );
1481
+ process.exitCode = 1;
1482
+ return;
1483
+ }
1484
+ const validLevels = ["basic", "standard", "full"];
1485
+ if (!validLevels.includes(options.level)) {
1486
+ outputError2(
1487
+ "Invalid conformance level",
1488
+ {
1489
+ level: options.level,
1490
+ valid: validLevels
1491
+ },
1492
+ globalOpts
1493
+ );
1494
+ process.exitCode = 1;
1495
+ return;
1496
+ }
1497
+ const callbacks = options.verbose ? {
1498
+ onTestStart: (testId) => {
1499
+ console.log(`Running: ${testId}`);
1500
+ },
1501
+ onTestComplete: (result) => {
1502
+ const status = result.status === "pass" ? "PASS" : result.status === "fail" ? "FAIL" : "SKIP";
1503
+ console.log(` ${status}: ${result.id}`);
1504
+ }
1505
+ } : void 0;
1506
+ const report = runConformance(
1507
+ {
1508
+ fixturesDir,
1509
+ level: options.level,
1510
+ category: options.category,
1511
+ implementationName: options.implementation,
1512
+ implementationVersion: getVersion()
1513
+ },
1514
+ callbacks
1515
+ );
1516
+ report.implementation.runtime = getRuntime();
1517
+ const commit = getCommit();
1518
+ if (commit) {
1519
+ report.implementation.commit = commit;
1520
+ }
1521
+ report.meta = {
1522
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
1523
+ runner: {
1524
+ name: "peac-cli",
1525
+ version: getVersion()
1526
+ },
1527
+ duration_ms: Date.now() - startTime
1528
+ };
1529
+ if (options.output === "json" || globalOpts.json) {
1530
+ console.log(JSON.stringify(report, null, 2));
1531
+ } else if (options.output === "markdown") {
1532
+ console.log(formatReportMarkdown(report));
1533
+ } else {
1534
+ console.log(formatReportText(report));
1535
+ }
1536
+ process.exitCode = report.summary.failed === 0 ? 0 : 1;
1537
+ } catch (err) {
1538
+ outputError2(err.message, {}, globalOpts);
1539
+ process.exitCode = 1;
1540
+ }
1541
+ });
1542
+ conformance.command("list").description("List available conformance test fixtures").option("-c, --category <category>", "List fixtures in specific category").option("--fixtures <path>", "Path to fixtures directory").action((options, cmd) => {
1543
+ const globalOpts = getGlobalOptions2(cmd);
1544
+ try {
1545
+ const fixturesDir = findFixturesDir(options.fixtures);
1546
+ if (!fixturesDir) {
1547
+ outputError2(
1548
+ "Fixtures directory not found",
1549
+ {
1550
+ hint: options.fixtures ? `Specified path does not exist: ${options.fixtures}` : "Run from repo root, specify --fixtures <path>, or ensure specs/conformance/fixtures is available"
1551
+ },
1552
+ globalOpts
1553
+ );
1554
+ process.exitCode = 1;
1555
+ return;
1556
+ }
1557
+ const manifestPath = path6.join(fixturesDir, "manifest.json");
1558
+ const manifest = fs6.existsSync(manifestPath) ? JSON.parse(fs6.readFileSync(manifestPath, "utf8")) : {};
1559
+ const categories = listCategories(fixturesDir);
1560
+ if (options.category) {
1561
+ if (!categories.includes(options.category)) {
1562
+ outputError2(
1563
+ "Category not found",
1564
+ {
1565
+ category: options.category,
1566
+ available: categories
1567
+ },
1568
+ globalOpts
1569
+ );
1570
+ process.exitCode = 1;
1571
+ return;
1572
+ }
1573
+ const categoryPath = path6.join(fixturesDir, options.category);
1574
+ const files = fs6.readdirSync(categoryPath).filter((f) => f.endsWith(".json"));
1575
+ const categoryManifest = manifest[options.category];
1576
+ if (globalOpts.json) {
1577
+ const fixtures = files.map((f) => ({
1578
+ file: f,
1579
+ description: categoryManifest?.[f]?.description || "No description"
1580
+ }));
1581
+ console.log(JSON.stringify({ category: options.category, fixtures }, null, 2));
1582
+ } else {
1583
+ console.log(`Fixtures in ${options.category}:
1584
+ `);
1585
+ for (const file of files) {
1586
+ const info = categoryManifest?.[file] || {};
1587
+ const desc = info.description || "No description";
1588
+ console.log(` ${file}`);
1589
+ console.log(` ${desc}
1590
+ `);
1591
+ }
1592
+ }
1593
+ } else {
1594
+ if (globalOpts.json) {
1595
+ const categorySummary = categories.map((cat) => ({
1596
+ name: cat,
1597
+ fixture_count: countFixtures(path6.join(fixturesDir, cat))
1598
+ }));
1599
+ console.log(JSON.stringify({ categories: categorySummary }, null, 2));
1600
+ } else {
1601
+ console.log(`Available conformance test categories:
1602
+ `);
1603
+ for (const cat of categories) {
1604
+ const count = countFixtures(path6.join(fixturesDir, cat));
1605
+ console.log(` ${cat} (${count} fixtures)`);
1606
+ }
1607
+ console.log(`
1608
+ Use 'peac conformance list -c <category>' to see fixtures in a category.`);
1609
+ }
1610
+ }
1611
+ process.exitCode = 0;
1612
+ } catch (err) {
1613
+ outputError2(err.message, {}, globalOpts);
1614
+ process.exitCode = 1;
1615
+ }
1616
+ });
1617
+ var EMBEDDED_SAMPLES = [
1618
+ {
1619
+ id: "basic-receipt",
1620
+ name: "Basic Receipt",
1621
+ description: "Minimal valid PEAC receipt with only required fields",
1622
+ category: "valid",
1623
+ claims: {
1624
+ iss: "https://sandbox.peacprotocol.org",
1625
+ aud: "https://example.com",
1626
+ iat: 0,
1627
+ // Placeholder - will be set at generation time
1628
+ exp: 0,
1629
+ // Placeholder - will be set at generation time
1630
+ rid: "sample-basic-001"
1631
+ }
1632
+ },
1633
+ {
1634
+ id: "full-receipt",
1635
+ name: "Full Receipt",
1636
+ description: "PEAC receipt with all optional claims populated",
1637
+ category: "valid",
1638
+ claims: {
1639
+ iss: "https://sandbox.peacprotocol.org",
1640
+ aud: "https://api.example.com",
1641
+ sub: "user:demo-user",
1642
+ iat: 0,
1643
+ exp: 0,
1644
+ rid: "sample-full-001",
1645
+ purpose_declared: ["search", "index"],
1646
+ purpose_enforced: "search",
1647
+ purpose_reason: "allowed"
1648
+ }
1649
+ },
1650
+ {
1651
+ id: "interaction-evidence",
1652
+ name: "Interaction Evidence",
1653
+ description: "Receipt with InteractionEvidence extension for AI agent calls",
1654
+ category: "valid",
1655
+ claims: {
1656
+ iss: "https://sandbox.peacprotocol.org",
1657
+ aud: "https://agent.example.com",
1658
+ sub: "agent:demo-agent-v1",
1659
+ iat: 0,
1660
+ exp: 0,
1661
+ rid: "sample-ie-001",
1662
+ ext: {
1663
+ "org.peacprotocol/interaction@0.1": {
1664
+ version: "0.1",
1665
+ interaction_id: "int_sample_001",
1666
+ started_at: "2026-01-01T00:00:00.000Z",
1667
+ completed_at: "2026-01-01T00:00:01.000Z",
1668
+ outcome: { kind: "success" },
1669
+ input: { hash: "sha256:abc123...", byte_length: 1024 },
1670
+ output: { hash: "sha256:def456...", byte_length: 2048 }
1671
+ }
1672
+ }
1673
+ }
1674
+ },
1675
+ {
1676
+ id: "payment-evidence",
1677
+ name: "Payment Evidence",
1678
+ description: "Receipt with payment evidence (402 flow)",
1679
+ category: "valid",
1680
+ claims: {
1681
+ iss: "https://sandbox.peacprotocol.org",
1682
+ aud: "https://api.example.com",
1683
+ iat: 0,
1684
+ exp: 0,
1685
+ rid: "sample-payment-001",
1686
+ amt: "100",
1687
+ cur: "USD",
1688
+ payment: {
1689
+ rail: "x402",
1690
+ reference: "pay_sample_001",
1691
+ amount: "100",
1692
+ currency: "USD"
1693
+ }
1694
+ }
1695
+ },
1696
+ {
1697
+ id: "long-expiry",
1698
+ name: "Long Expiry",
1699
+ description: "Receipt with 24-hour expiration",
1700
+ category: "valid",
1701
+ claims: {
1702
+ iss: "https://sandbox.peacprotocol.org",
1703
+ aud: "https://example.com",
1704
+ iat: 0,
1705
+ exp: 0,
1706
+ // Will be set to iat + 86400 at generation time
1707
+ rid: "sample-long-expiry-001"
1708
+ }
1709
+ },
1710
+ {
1711
+ id: "expired",
1712
+ name: "Expired Receipt",
1713
+ description: "Receipt that has already expired (for testing rejection)",
1714
+ category: "invalid",
1715
+ claims: {
1716
+ iss: "https://sandbox.peacprotocol.org",
1717
+ aud: "https://example.com",
1718
+ iat: 0,
1719
+ // Will be set to now - 7200 at generation time
1720
+ exp: 0,
1721
+ // Will be set to now - 3600 at generation time
1722
+ rid: "sample-expired-001"
1723
+ },
1724
+ expectedError: "E_EXPIRED_RECEIPT"
1725
+ },
1726
+ {
1727
+ id: "future-iat",
1728
+ name: "Future IAT",
1729
+ description: "Receipt with iat in the future (should be rejected)",
1730
+ category: "invalid",
1731
+ claims: {
1732
+ iss: "https://sandbox.peacprotocol.org",
1733
+ aud: "https://example.com",
1734
+ iat: 0,
1735
+ // Will be set to now + 3600 at generation time
1736
+ exp: 0,
1737
+ // Will be set to now + 7200 at generation time
1738
+ rid: "sample-future-iat-001"
1739
+ },
1740
+ expectedError: "E_FUTURE_IAT"
1741
+ },
1742
+ {
1743
+ id: "missing-iss",
1744
+ name: "Missing Issuer",
1745
+ description: "Receipt missing required iss claim (for testing validation)",
1746
+ category: "invalid",
1747
+ claims: {
1748
+ aud: "https://example.com",
1749
+ iat: 0,
1750
+ exp: 0,
1751
+ rid: "sample-no-iss-001"
1752
+ },
1753
+ expectedError: "E_MISSING_CLAIM"
1754
+ }
1755
+ ];
1756
+ function findSamplesDir(customPath) {
1757
+ if (customPath) {
1758
+ if (fs6.existsSync(customPath)) {
1759
+ return customPath;
1760
+ }
1761
+ return null;
1762
+ }
1763
+ const cliPath = path6.resolve(__dirname, "../../../../specs/conformance/samples");
1764
+ if (fs6.existsSync(cliPath)) {
1765
+ return cliPath;
1766
+ }
1767
+ const repoPath = path6.resolve(process.cwd(), "specs/conformance/samples");
1768
+ if (fs6.existsSync(repoPath)) {
1769
+ return repoPath;
1770
+ }
1771
+ const upPath = path6.resolve(process.cwd(), "../../specs/conformance/samples");
1772
+ if (fs6.existsSync(upPath)) {
1773
+ return upPath;
1774
+ }
1775
+ return null;
1776
+ }
1777
+ function loadSampleFromFile(filePath, id, category) {
1778
+ try {
1779
+ const content = JSON.parse(fs6.readFileSync(filePath, "utf8"));
1780
+ const claims = content.payload ?? content.claims ?? content;
1781
+ const description = content.$comment ?? content.description ?? `Sample ${id}`;
1782
+ return {
1783
+ id,
1784
+ name: id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
1785
+ description,
1786
+ category,
1787
+ claims,
1788
+ header: content.header,
1789
+ expectedError: content.expected_error
1790
+ };
1791
+ } catch {
1792
+ return null;
1793
+ }
1794
+ }
1795
+ function loadSamplesFromDir(samplesDir) {
1796
+ const samples2 = [];
1797
+ const validDir = path6.join(samplesDir, "valid");
1798
+ if (fs6.existsSync(validDir)) {
1799
+ const files = fs6.readdirSync(validDir).filter((f) => f.endsWith(".json"));
1800
+ for (const file of files) {
1801
+ const id = file.replace(".json", "");
1802
+ const sample = loadSampleFromFile(path6.join(validDir, file), id, "valid");
1803
+ if (sample) samples2.push(sample);
1804
+ }
1805
+ }
1806
+ const invalidDir = path6.join(samplesDir, "invalid");
1807
+ if (fs6.existsSync(invalidDir)) {
1808
+ const files = fs6.readdirSync(invalidDir).filter((f) => f.endsWith(".json"));
1809
+ for (const file of files) {
1810
+ const id = file.replace(".json", "");
1811
+ const sample = loadSampleFromFile(path6.join(invalidDir, file), id, "invalid");
1812
+ if (sample) samples2.push(sample);
1813
+ }
1814
+ }
1815
+ const edgeDir = path6.join(samplesDir, "edge");
1816
+ if (fs6.existsSync(edgeDir)) {
1817
+ const files = fs6.readdirSync(edgeDir).filter((f) => f.endsWith(".json"));
1818
+ for (const file of files) {
1819
+ const id = file.replace(".json", "");
1820
+ const sample = loadSampleFromFile(path6.join(edgeDir, file), id, "edge");
1821
+ if (sample) samples2.push(sample);
1822
+ }
1823
+ }
1824
+ return samples2;
1825
+ }
1826
+ function getSamples(customSamplesPath) {
1827
+ const samplesDir = findSamplesDir(customSamplesPath);
1828
+ if (samplesDir) {
1829
+ const dirSamples = loadSamplesFromDir(samplesDir);
1830
+ if (dirSamples.length > 0) {
1831
+ return dirSamples;
1832
+ }
1833
+ }
1834
+ return EMBEDDED_SAMPLES;
1835
+ }
1836
+ function getSampleById(id, customSamplesPath) {
1837
+ const samples2 = getSamples(customSamplesPath);
1838
+ return samples2.find((s) => s.id === id) ?? null;
1839
+ }
1840
+
1841
+ // src/commands/samples.ts
1842
+ function getGlobalOptions3(cmd) {
1843
+ const parent = cmd.parent;
1844
+ if (!parent) return {};
1845
+ return parent.opts();
1846
+ }
1847
+ function outputError3(error, details, opts) {
1848
+ if (opts.json) {
1849
+ console.log(JSON.stringify({ success: false, error, ...details }, null, 2));
1850
+ } else {
1851
+ console.error(`Error: ${error}`);
1852
+ if (details.hint) {
1853
+ console.error(`Hint: ${details.hint}`);
1854
+ }
1855
+ }
1856
+ }
1857
+ function applyTimeAdjustments(claims, sampleId, now) {
1858
+ const adjusted = { ...claims };
1859
+ const standardExpiry = 3600;
1860
+ const longExpiry = 86400;
1861
+ switch (sampleId) {
1862
+ case "expired":
1863
+ adjusted.iat = now - 7200;
1864
+ adjusted.exp = now - 3600;
1865
+ break;
1866
+ case "future-iat":
1867
+ adjusted.iat = now + 3600;
1868
+ adjusted.exp = now + 7200;
1869
+ break;
1870
+ case "long-expiry":
1871
+ adjusted.iat = now;
1872
+ adjusted.exp = now + longExpiry;
1873
+ break;
1874
+ default:
1875
+ if (adjusted.iat === 0 || !adjusted.iat) {
1876
+ adjusted.iat = now;
1877
+ }
1878
+ if (adjusted.exp === 0 || !adjusted.exp) {
1879
+ adjusted.exp = now + standardExpiry;
1880
+ }
1881
+ }
1882
+ return adjusted;
1883
+ }
1884
+ var samples = new Command("samples").description("Work with PEAC sample receipts (v0.10.8+)").option("--json", "Output in JSON format");
1885
+ samples.command("list").description("List available sample receipts").option("-c, --category <category>", "Filter by category: valid, invalid, edge").option("--samples <path>", "Path to samples directory").action((options, cmd) => {
1886
+ const globalOpts = getGlobalOptions3(cmd);
1887
+ try {
1888
+ const allSamples = getSamples(options.samples);
1889
+ let filteredSamples = allSamples;
1890
+ if (options.category) {
1891
+ filteredSamples = allSamples.filter((s) => s.category === options.category);
1892
+ if (filteredSamples.length === 0) {
1893
+ outputError3("No samples in category", { category: options.category }, globalOpts);
1894
+ process.exitCode = 1;
1895
+ return;
1896
+ }
1897
+ }
1898
+ if (globalOpts.json) {
1899
+ const data = filteredSamples.map((s) => ({
1900
+ id: s.id,
1901
+ name: s.name,
1902
+ description: s.description,
1903
+ category: s.category
1904
+ }));
1905
+ console.log(JSON.stringify({ samples: data }, null, 2));
1906
+ } else {
1907
+ console.log("Available PEAC Sample Receipts\n");
1908
+ console.log("VALID SAMPLES:");
1909
+ for (const s of filteredSamples.filter((x) => x.category === "valid")) {
1910
+ console.log(` ${s.id}`);
1911
+ console.log(` ${s.description}
1912
+ `);
1913
+ }
1914
+ const invalid = filteredSamples.filter((x) => x.category === "invalid");
1915
+ if (invalid.length > 0) {
1916
+ console.log("\nINVALID SAMPLES (for testing rejection):");
1917
+ for (const s of invalid) {
1918
+ console.log(` ${s.id}`);
1919
+ console.log(` ${s.description}
1920
+ `);
1921
+ }
1922
+ }
1923
+ const edge = filteredSamples.filter((x) => x.category === "edge");
1924
+ if (edge.length > 0) {
1925
+ console.log("\nEDGE CASE SAMPLES:");
1926
+ for (const s of edge) {
1927
+ console.log(` ${s.id}`);
1928
+ console.log(` ${s.description}
1929
+ `);
1930
+ }
1931
+ }
1932
+ console.log('\nUse "peac samples show <id>" to see sample details.');
1933
+ console.log('Use "peac samples generate -o <dir>" to generate sample files.');
1934
+ }
1935
+ process.exitCode = 0;
1936
+ } catch (err) {
1937
+ outputError3(err.message, {}, globalOpts);
1938
+ process.exitCode = 1;
1939
+ }
1940
+ });
1941
+ samples.command("show").description("Show details of a specific sample").argument("<id>", "Sample ID").option("--samples <path>", "Path to samples directory").action((id, options, cmd) => {
1942
+ const globalOpts = getGlobalOptions3(cmd);
1943
+ try {
1944
+ const sample = getSampleById(id, options.samples);
1945
+ if (!sample) {
1946
+ const allSamples = getSamples(options.samples);
1947
+ const available = allSamples.map((s) => s.id);
1948
+ outputError3("Sample not found", { id, available }, globalOpts);
1949
+ process.exitCode = 1;
1950
+ return;
1951
+ }
1952
+ if (globalOpts.json) {
1953
+ console.log(JSON.stringify(sample, null, 2));
1954
+ } else {
1955
+ console.log(`Sample: ${sample.name}
1956
+ `);
1957
+ console.log(`ID: ${sample.id}`);
1958
+ console.log(`Category: ${sample.category}`);
1959
+ console.log(`Description: ${sample.description}
1960
+ `);
1961
+ console.log("Claims:");
1962
+ console.log(JSON.stringify(sample.claims, null, 2));
1963
+ if (sample.expectedError) {
1964
+ console.log(`
1965
+ Expected Error: ${sample.expectedError}`);
1966
+ }
1967
+ }
1968
+ process.exitCode = 0;
1969
+ } catch (err) {
1970
+ outputError3(err.message, {}, globalOpts);
1971
+ process.exitCode = 1;
1972
+ }
1973
+ });
1974
+ samples.command("generate").description("Generate sample receipt files").requiredOption("-o, --output <dir>", "Output directory").option("-f, --format <format>", "Output format: jws, json, bundle", "jws").option("--category <category>", "Generate only specific category").option("--samples <path>", "Path to samples directory").option("--now <timestamp>", "Unix timestamp for iat/exp (for deterministic generation)").option("--kid <kid>", "Key ID to use").action(async (options, cmd) => {
1975
+ const globalOpts = getGlobalOptions3(cmd);
1976
+ try {
1977
+ const outputDir = options.output;
1978
+ const validDir = path6.join(outputDir, "valid");
1979
+ const invalidDir = path6.join(outputDir, "invalid");
1980
+ const edgeDir = path6.join(outputDir, "edge");
1981
+ const bundlesDir = path6.join(outputDir, "bundles");
1982
+ fs6.mkdirSync(validDir, { recursive: true });
1983
+ fs6.mkdirSync(invalidDir, { recursive: true });
1984
+ fs6.mkdirSync(edgeDir, { recursive: true });
1985
+ fs6.mkdirSync(bundlesDir, { recursive: true });
1986
+ const now = options.now ? parseInt(options.now, 10) : Math.floor(Date.now() / 1e3);
1987
+ const keyPair = await generateKeypair();
1988
+ const publicKeyBytes = keyPair.publicKey;
1989
+ const privateKeyBytes = keyPair.privateKey;
1990
+ const kid = options.kid || `sandbox-${new Date(now * 1e3).toISOString().slice(0, 7)}`;
1991
+ const publicJwk = {
1992
+ kty: "OKP",
1993
+ crv: "Ed25519",
1994
+ x: base64urlEncode(publicKeyBytes),
1995
+ kid,
1996
+ use: "sig",
1997
+ alg: "EdDSA"
1998
+ };
1999
+ const generatedFiles = [];
2000
+ let samplesToGenerate = getSamples(options.samples);
2001
+ if (options.category) {
2002
+ samplesToGenerate = samplesToGenerate.filter((s) => s.category === options.category);
2003
+ }
2004
+ for (const sample of samplesToGenerate) {
2005
+ const targetDir = sample.category === "valid" ? validDir : sample.category === "edge" ? edgeDir : invalidDir;
2006
+ const filename = `${sample.id}.${options.format === "json" ? "json" : "jws"}`;
2007
+ const filepath = path6.join(targetDir, filename);
2008
+ const adjustedClaims = applyTimeAdjustments(sample.claims, sample.id, now);
2009
+ if (options.format === "json") {
2010
+ const output2 = {
2011
+ $comment: sample.description,
2012
+ header: {
2013
+ alg: "EdDSA",
2014
+ typ: "peac-receipt/0.1",
2015
+ kid
2016
+ },
2017
+ payload: adjustedClaims,
2018
+ ...sample.expectedError ? { expected_error: sample.expectedError } : {}
2019
+ };
2020
+ fs6.writeFileSync(filepath, JSON.stringify(output2, null, 2));
2021
+ } else {
2022
+ const jws = await sign(adjustedClaims, privateKeyBytes, kid);
2023
+ fs6.writeFileSync(filepath, jws);
2024
+ }
2025
+ generatedFiles.push(filepath);
2026
+ }
2027
+ const jwksPath = path6.join(bundlesDir, "sandbox-jwks.json");
2028
+ const jwks = {
2029
+ keys: [publicJwk]
2030
+ };
2031
+ fs6.writeFileSync(jwksPath, JSON.stringify(jwks, null, 2));
2032
+ generatedFiles.push(jwksPath);
2033
+ const bundlePath = path6.join(bundlesDir, "offline-verification.json");
2034
+ const bundle = {
2035
+ $comment: "Offline verification bundle with sample receipts and JWKS",
2036
+ description: "Bundle for offline verification testing. Contains test JWKS and sample metadata.",
2037
+ generated_at: new Date(now * 1e3).toISOString(),
2038
+ generator: {
2039
+ name: "peac-cli",
2040
+ version: getVersion()
2041
+ },
2042
+ jwks,
2043
+ samples: samplesToGenerate.map((s) => ({
2044
+ id: s.id,
2045
+ description: s.description,
2046
+ category: s.category
2047
+ })),
2048
+ notes: [
2049
+ "Sample timestamps are based on --now parameter or generation time.",
2050
+ "Do NOT use sandbox samples in production."
2051
+ ]
2052
+ };
2053
+ fs6.writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
2054
+ generatedFiles.push(bundlePath);
2055
+ if (globalOpts.json) {
2056
+ console.log(
2057
+ JSON.stringify(
2058
+ {
2059
+ success: true,
2060
+ output_dir: outputDir,
2061
+ files_generated: generatedFiles.length,
2062
+ files: generatedFiles,
2063
+ timestamp: now,
2064
+ kid
2065
+ },
2066
+ null,
2067
+ 2
2068
+ )
2069
+ );
2070
+ } else {
2071
+ console.log(`Sample receipts generated successfully!
2072
+ `);
2073
+ console.log(`Output directory: ${outputDir}`);
2074
+ console.log(`Files generated: ${generatedFiles.length}`);
2075
+ console.log(`Timestamp: ${now} (${new Date(now * 1e3).toISOString()})`);
2076
+ console.log(`Key ID: ${kid}
2077
+ `);
2078
+ console.log("Generated files:");
2079
+ for (const f of generatedFiles) {
2080
+ console.log(` ${path6.relative(outputDir, f)}`);
2081
+ }
2082
+ console.log(`
2083
+ JWKS for verification: ${path6.relative(outputDir, jwksPath)}`);
2084
+ }
2085
+ process.exitCode = 0;
2086
+ } catch (err) {
2087
+ outputError3(err.message, {}, globalOpts);
2088
+ process.exitCode = 1;
2089
+ }
2090
+ });
2091
+
2092
+ // src/index.ts
2093
+ var program = new Command();
2094
+ program.name("peac").description("PEAC protocol command-line tools").version(getVersion()).exitOverride();
2095
+ program.command("verify").description("Verify a PEAC receipt JWS").argument("<jws>", "JWS compact serialization or path to file containing JWS").option("-v, --verbose", "Show detailed output").action(async (jwsInput, options) => {
2096
+ try {
2097
+ let jws = jwsInput;
2098
+ if (fs6.existsSync(jwsInput)) {
2099
+ jws = fs6.readFileSync(jwsInput, "utf-8").trim();
2100
+ }
2101
+ console.log("Verifying PEAC receipt...\n");
2102
+ const { header, payload } = decode(jws);
2103
+ console.log("Receipt Information:");
2104
+ console.log(` Receipt ID: ${payload.rid}`);
2105
+ console.log(` Issuer: ${payload.iss}`);
2106
+ console.log(` Audience: ${payload.aud}`);
2107
+ console.log(` Amount: ${payload.amt} ${payload.cur}`);
2108
+ console.log(` Payment: ${payload.payment.rail} (${payload.payment.reference})`);
2109
+ console.log(` Issued: ${new Date(payload.iat * 1e3).toISOString()}`);
2110
+ if (payload.exp) {
2111
+ console.log(` Expires: ${new Date(payload.exp * 1e3).toISOString()}`);
2112
+ }
2113
+ console.log();
2114
+ console.log("Verifying signature...");
2115
+ const result = await verifyReceipt(jws);
2116
+ if (result.ok) {
2117
+ console.log("Signature valid!");
2118
+ if (result.perf) {
2119
+ console.log(` Verification time: ${result.perf.verify_ms.toFixed(2)}ms`);
2120
+ if (result.perf.jwks_fetch_ms) {
2121
+ console.log(` JWKS fetch time: ${result.perf.jwks_fetch_ms.toFixed(2)}ms`);
2122
+ }
2123
+ }
2124
+ process.exitCode = 0;
2125
+ } else {
2126
+ console.log(`Verification failed: ${result.reason}`);
2127
+ if (result.details) {
2128
+ console.log(` Details: ${result.details}`);
2129
+ }
2130
+ process.exitCode = 1;
2131
+ }
2132
+ } catch (err) {
2133
+ console.error("Error:", err instanceof Error ? err.message : String(err));
2134
+ process.exitCode = 1;
2135
+ }
2136
+ });
2137
+ program.command("validate-issuer").description("Validate a PEAC issuer configuration").argument("<input>", "Path to peac-issuer.json file or issuer URL").action(async (input) => {
2138
+ try {
2139
+ console.log("Validating issuer configuration...\n");
2140
+ let config;
2141
+ if (input.startsWith("http://") || input.startsWith("https://")) {
2142
+ console.log(`Fetching from ${input}...`);
2143
+ config = await fetchIssuerConfig(input);
2144
+ } else {
2145
+ const text = fs6.readFileSync(input, "utf-8");
2146
+ config = parseIssuerConfig(text);
2147
+ }
2148
+ console.log("Issuer configuration is valid!\n");
2149
+ console.log("Issuer Configuration:");
2150
+ console.log(` Version: ${config.version}`);
2151
+ console.log(` Issuer: ${config.issuer}`);
2152
+ console.log(` JWKS URI: ${config.jwks_uri}`);
2153
+ if (config.verify_endpoint) {
2154
+ console.log(` Verify: ${config.verify_endpoint}`);
2155
+ }
2156
+ if (config.receipt_versions && config.receipt_versions.length > 0) {
2157
+ console.log(` Receipts: ${config.receipt_versions.join(", ")}`);
2158
+ }
2159
+ if (config.algorithms && config.algorithms.length > 0) {
2160
+ console.log(` Algorithms: ${config.algorithms.join(", ")}`);
2161
+ }
2162
+ if (config.payment_rails && config.payment_rails.length > 0) {
2163
+ console.log(` Rails: ${config.payment_rails.join(", ")}`);
2164
+ }
2165
+ if (config.security_contact) {
2166
+ console.log(` Security: ${config.security_contact}`);
2167
+ }
2168
+ process.exitCode = 0;
2169
+ } catch (err) {
2170
+ console.error("Error:", err instanceof Error ? err.message : String(err));
2171
+ process.exitCode = 1;
2172
+ }
2173
+ });
2174
+ program.command("decode").description("Decode a PEAC receipt JWS (without verification)").argument("<jws>", "JWS compact serialization or path to file containing JWS").option("--json", "Output as JSON").action((jwsInput, options) => {
2175
+ try {
2176
+ let jws = jwsInput;
2177
+ if (fs6.existsSync(jwsInput)) {
2178
+ jws = fs6.readFileSync(jwsInput, "utf-8").trim();
2179
+ }
2180
+ const { header, payload } = decode(jws);
2181
+ if (options.json) {
2182
+ console.log(JSON.stringify({ header, payload }, null, 2));
2183
+ } else {
2184
+ console.log("PEAC Receipt (Decoded, Not Verified)\n");
2185
+ console.log("Header:");
2186
+ console.log(` typ: ${header.typ}`);
2187
+ console.log(` alg: ${header.alg}`);
2188
+ console.log(` kid: ${header.kid}`);
2189
+ console.log();
2190
+ console.log("Claims:");
2191
+ console.log(` iss: ${payload.iss}`);
2192
+ console.log(` aud: ${payload.aud}`);
2193
+ console.log(` iat: ${payload.iat} (${new Date(payload.iat * 1e3).toISOString()})`);
2194
+ if (payload.exp) {
2195
+ console.log(` exp: ${payload.exp} (${new Date(payload.exp * 1e3).toISOString()})`);
2196
+ }
2197
+ console.log(` rid: ${payload.rid}`);
2198
+ console.log(` amt: ${payload.amt}`);
2199
+ console.log(` cur: ${payload.cur}`);
2200
+ console.log();
2201
+ console.log("Payment:");
2202
+ console.log(` rail: ${payload.payment.rail}`);
2203
+ console.log(` reference: ${payload.payment.reference}`);
2204
+ console.log(` amount: ${payload.payment.amount}`);
2205
+ console.log(` currency: ${payload.payment.currency}`);
2206
+ if (payload.subject) {
2207
+ console.log();
2208
+ console.log("Subject:");
2209
+ console.log(` uri: ${payload.subject.uri}`);
2210
+ }
2211
+ if (payload.ext) {
2212
+ console.log();
2213
+ console.log("Extensions:");
2214
+ console.log(JSON.stringify(payload.ext, null, 2));
2215
+ }
2216
+ }
2217
+ process.exitCode = 0;
2218
+ } catch (err) {
2219
+ console.error("Error:", err instanceof Error ? err.message : String(err));
2220
+ process.exitCode = 1;
2221
+ }
2222
+ });
2223
+ program.addCommand(policy);
2224
+ program.addCommand(conformance);
2225
+ program.addCommand(samples);
2226
+ try {
2227
+ program.parse();
2228
+ } catch (err) {
2229
+ if (err instanceof CommanderError) {
2230
+ process.exitCode = err.exitCode;
2231
+ } else {
2232
+ throw err;
2233
+ }
2234
+ }
2235
+ //# sourceMappingURL=index.mjs.map
2236
+ //# sourceMappingURL=index.mjs.map