@peac/cli 0.12.14 → 0.13.1
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/README.md +1 -1
- package/dist/commands/discover.d.ts +7 -4
- package/dist/commands/discover.d.ts.map +1 -1
- package/dist/index.cjs +243 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +241 -4
- package/dist/index.mjs.map +1 -1
- package/dist/lib/conformance/validators.d.ts.map +1 -1
- package/dist/lib/conformance-runner.d.ts +2 -1
- package/dist/lib/conformance-runner.d.ts.map +1 -1
- package/dist/lib/policy-document-discovery.d.ts +68 -0
- package/dist/lib/policy-document-discovery.d.ts.map +1 -0
- package/package.json +8 -7
package/dist/index.mjs
CHANGED
|
@@ -6,13 +6,14 @@ import * as fs6 from 'fs';
|
|
|
6
6
|
import { existsSync, readFileSync } from 'fs';
|
|
7
7
|
import * as path6 from 'path';
|
|
8
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';
|
|
9
|
+
import { loadProfile, serializePolicyJson, serializePolicyYaml, ProfileError, createExamplePolicy, loadPolicy, PolicyValidationError, PolicyLoadError, compilePeacTxt, compileRobotsSnippet, compileAiprefTemplates, renderPolicyMarkdown, explainMatches, evaluate, listProfiles, getProfileSummary, parsePolicyDocument } from '@peac/policy-kit';
|
|
10
10
|
import { execSync } from 'child_process';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
12
|
import { computeReceiptRef, validateWorkflowContextOrdered, isValidPurposeToken, isCanonicalPurpose, validateObligationsExtension, validateInteractionOrdered, validateDisputeAttestation, validateAttributionAttestation, validateAgentIdentityAttestation, ReceiptClaimsSchema, parseReceiptClaims } from '@peac/schema';
|
|
13
|
-
import {
|
|
13
|
+
import { POLICY } from '@peac/kernel';
|
|
14
14
|
import { readDisputeBundle } from '@peac/audit';
|
|
15
15
|
import { createRequire } from 'module';
|
|
16
|
+
import chalk from 'chalk';
|
|
16
17
|
|
|
17
18
|
function getGlobalOptions(cmd) {
|
|
18
19
|
const parent = cmd.parent;
|
|
@@ -682,7 +683,7 @@ var PROFILE_CAPABILITIES = {
|
|
|
682
683
|
discovery: {
|
|
683
684
|
profile: "discovery.config",
|
|
684
685
|
level: "semantic",
|
|
685
|
-
validator: "@peac/
|
|
686
|
+
validator: "@peac/policy-kit:parsePolicyDocument (via cli compat helper)"
|
|
686
687
|
},
|
|
687
688
|
dispute: {
|
|
688
689
|
profile: "receipt.verify.dispute",
|
|
@@ -762,6 +763,124 @@ function shouldRunAtLevel(fixtureVersion, level) {
|
|
|
762
763
|
const levelOrder = ["basic", "standard", "full"];
|
|
763
764
|
return levelOrder.indexOf(fixtureLevel) <= levelOrder.indexOf(level);
|
|
764
765
|
}
|
|
766
|
+
var DISCOVER_TIMEOUT_MS = 5e3;
|
|
767
|
+
var DISCOVER_MAX_BYTES = POLICY.maxBytes;
|
|
768
|
+
var PEAC_TXT_PATH = POLICY.manifestPath;
|
|
769
|
+
var DEFAULT_USER_AGENT = "peac-cli";
|
|
770
|
+
var LEGACY_KEY_LINE = /^\s*(verify|public_keys|jwks)\s*:/m;
|
|
771
|
+
var LEGACY_KEY_FULL_LINE_GLOBAL = /^\s*(verify|public_keys|jwks)\s*:.*\r?\n?/gm;
|
|
772
|
+
var legacyWarningFired = false;
|
|
773
|
+
function fireLegacyWarning(field) {
|
|
774
|
+
if (legacyWarningFired) return;
|
|
775
|
+
legacyWarningFired = true;
|
|
776
|
+
if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
|
|
777
|
+
process.emitWarning(
|
|
778
|
+
`peac.txt legacy key-discovery field "${field}" is deprecated and ignored. peac.txt is a policy-document surface (docs/specs/PEAC-TXT.md). Key resolution uses iss -> /.well-known/peac-issuer.json -> jwks_uri -> JWKS.`,
|
|
779
|
+
{ code: "PEAC_LEGACY_PEAC_TXT_KEY_FIELD", type: "DeprecationWarning" }
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function collectLegacyWarnings(text, warnings) {
|
|
784
|
+
const lines = text.split(/\r?\n/);
|
|
785
|
+
let firstField = null;
|
|
786
|
+
lines.forEach((line, idx) => {
|
|
787
|
+
const m = line.match(/^\s*(verify|public_keys|jwks)\s*:/);
|
|
788
|
+
if (m) {
|
|
789
|
+
if (firstField === null) firstField = m[1];
|
|
790
|
+
warnings.push(
|
|
791
|
+
`Line ${idx + 1}: legacy key-discovery field "${m[1]}" ignored (peac.txt is policy-only; use peac-issuer.json for keys)`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
if (firstField) fireLegacyWarning(firstField);
|
|
796
|
+
}
|
|
797
|
+
function failureFrom(err, warnings) {
|
|
798
|
+
const errors = [];
|
|
799
|
+
if (err instanceof PolicyValidationError) {
|
|
800
|
+
const issues = err.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
801
|
+
errors.push(`Policy validation failed: ${issues}`);
|
|
802
|
+
} else if (err instanceof PolicyLoadError) {
|
|
803
|
+
errors.push(err.message);
|
|
804
|
+
} else {
|
|
805
|
+
errors.push(`Unexpected parse error: ${err instanceof Error ? err.message : String(err)}`);
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
valid: false,
|
|
809
|
+
errors,
|
|
810
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function parsePolicyDocumentCompat(text) {
|
|
814
|
+
if (text.trim().length === 0) {
|
|
815
|
+
return {
|
|
816
|
+
valid: false,
|
|
817
|
+
errors: ["Empty policy document. Expected peac-policy/0.1 YAML or JSON."]
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
const hasLegacyLines = LEGACY_KEY_LINE.test(text);
|
|
821
|
+
const warnings = [];
|
|
822
|
+
try {
|
|
823
|
+
const data = parsePolicyDocument(text);
|
|
824
|
+
if (hasLegacyLines) collectLegacyWarnings(text, warnings);
|
|
825
|
+
return {
|
|
826
|
+
valid: true,
|
|
827
|
+
data,
|
|
828
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
829
|
+
};
|
|
830
|
+
} catch (firstErr) {
|
|
831
|
+
if (!hasLegacyLines) return failureFrom(firstErr, warnings);
|
|
832
|
+
collectLegacyWarnings(text, warnings);
|
|
833
|
+
const stripped = text.replace(LEGACY_KEY_FULL_LINE_GLOBAL, "");
|
|
834
|
+
try {
|
|
835
|
+
const data = parsePolicyDocument(stripped);
|
|
836
|
+
return { valid: true, data, warnings };
|
|
837
|
+
} catch (secondErr) {
|
|
838
|
+
return failureFrom(secondErr, warnings);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async function fetchPolicyDocumentText(origin, options = {}) {
|
|
843
|
+
let target;
|
|
844
|
+
try {
|
|
845
|
+
target = new URL(PEAC_TXT_PATH, origin);
|
|
846
|
+
} catch (err) {
|
|
847
|
+
return {
|
|
848
|
+
ok: false,
|
|
849
|
+
code: "INVALID_URL",
|
|
850
|
+
error: `Invalid origin: ${err instanceof Error ? err.message : String(err)}`
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
const fetchOptions = {
|
|
854
|
+
timeoutMs: options.timeoutMs ?? DISCOVER_TIMEOUT_MS,
|
|
855
|
+
maxResponseBytes: options.maxBytes ?? DISCOVER_MAX_BYTES,
|
|
856
|
+
headers: { "user-agent": options.userAgent ?? DEFAULT_USER_AGENT },
|
|
857
|
+
allowedMethods: ["GET"]
|
|
858
|
+
};
|
|
859
|
+
const { safeFetchRaw } = await import('@peac/net-node');
|
|
860
|
+
const raw = await safeFetchRaw(target.toString(), fetchOptions);
|
|
861
|
+
if (!raw.ok) {
|
|
862
|
+
return {
|
|
863
|
+
ok: false,
|
|
864
|
+
code: raw.code,
|
|
865
|
+
error: `${raw.code}: ${raw.error}`
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
try {
|
|
869
|
+
if (!raw.response.ok) {
|
|
870
|
+
return {
|
|
871
|
+
ok: false,
|
|
872
|
+
code: `HTTP_${raw.response.status}`,
|
|
873
|
+
error: `HTTP ${raw.response.status}: ${raw.response.statusText}`
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
const text = await raw.response.text();
|
|
877
|
+
return { ok: true, text, warnings: raw.warnings };
|
|
878
|
+
} finally {
|
|
879
|
+
await raw.close();
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/lib/conformance/validators.ts
|
|
765
884
|
function zodCodeToKeyword(code) {
|
|
766
885
|
const mapping = {
|
|
767
886
|
invalid_type: "type",
|
|
@@ -816,7 +935,7 @@ function validateParseReceiptClaims(payload) {
|
|
|
816
935
|
}
|
|
817
936
|
function validateDiscoveryInput(input) {
|
|
818
937
|
if (typeof input === "string") {
|
|
819
|
-
const result =
|
|
938
|
+
const result = parsePolicyDocumentCompat(input);
|
|
820
939
|
if (result.valid) {
|
|
821
940
|
return { valid: true };
|
|
822
941
|
}
|
|
@@ -2572,6 +2691,118 @@ function doctor(program2) {
|
|
|
2572
2691
|
process.exit(exitCode);
|
|
2573
2692
|
});
|
|
2574
2693
|
}
|
|
2694
|
+
function formatOutput(result, json = false) {
|
|
2695
|
+
if (json) {
|
|
2696
|
+
return JSON.stringify(result, null, 2);
|
|
2697
|
+
}
|
|
2698
|
+
if (!result.success) {
|
|
2699
|
+
return chalk.red(`Error: ${result.error}`);
|
|
2700
|
+
}
|
|
2701
|
+
if (result.data && typeof result.data === "object") {
|
|
2702
|
+
if (result.data.sources) {
|
|
2703
|
+
return formatDiscoveryResult(result.data);
|
|
2704
|
+
} else if (result.data.digest) {
|
|
2705
|
+
return formatHashResult(result.data);
|
|
2706
|
+
} else if (result.data.valid !== void 0) {
|
|
2707
|
+
return formatVerifyResult(result.data);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
return JSON.stringify(result.data, null, 2);
|
|
2711
|
+
}
|
|
2712
|
+
function formatDiscoveryResult(data) {
|
|
2713
|
+
const lines = [`Discovery for: ${chalk.blue(data.url)}`, ""];
|
|
2714
|
+
for (const source of data.sources) {
|
|
2715
|
+
const status = source.status === "found" ? chalk.green("[found]") : source.status === "not_found" ? chalk.yellow("[not found]") : chalk.red("[error]");
|
|
2716
|
+
lines.push(`${status} ${source.type}: ${source.url}`);
|
|
2717
|
+
if (source.etag) {
|
|
2718
|
+
lines.push(` ETag: ${source.etag}`);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
return lines.join("\\n");
|
|
2722
|
+
}
|
|
2723
|
+
function formatHashResult(data) {
|
|
2724
|
+
return [
|
|
2725
|
+
`Algorithm: ${data.algorithm}`,
|
|
2726
|
+
`Format: ${data.format}`,
|
|
2727
|
+
`Digest: ${chalk.green(data.digest)}`,
|
|
2728
|
+
`Input size: ${data.input_size} bytes`
|
|
2729
|
+
].join("\\n");
|
|
2730
|
+
}
|
|
2731
|
+
function formatVerifyResult(data) {
|
|
2732
|
+
if (data.valid) {
|
|
2733
|
+
const lines = [chalk.green("Receipt verified successfully"), ""];
|
|
2734
|
+
if (data.policy_hash) {
|
|
2735
|
+
lines.push(`Policy hash: ${data.policy_hash}`);
|
|
2736
|
+
}
|
|
2737
|
+
if (data.resource) {
|
|
2738
|
+
lines.push(`Resource: ${data.resource}`);
|
|
2739
|
+
}
|
|
2740
|
+
return lines.join("\\n");
|
|
2741
|
+
} else {
|
|
2742
|
+
return chalk.red(`Verification failed: ${data.error || "Unknown error"}`);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
function handleError(error) {
|
|
2746
|
+
return {
|
|
2747
|
+
success: false,
|
|
2748
|
+
error: error.message
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
function timing() {
|
|
2752
|
+
const started = Date.now();
|
|
2753
|
+
return {
|
|
2754
|
+
started,
|
|
2755
|
+
end: () => {
|
|
2756
|
+
const completed = Date.now();
|
|
2757
|
+
return {
|
|
2758
|
+
started,
|
|
2759
|
+
completed,
|
|
2760
|
+
duration: completed - started
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/commands/discover.ts
|
|
2767
|
+
var DiscoverCommand = class {
|
|
2768
|
+
async execute(url, _options = {}) {
|
|
2769
|
+
const timer = timing();
|
|
2770
|
+
try {
|
|
2771
|
+
const parsedUrl = new URL(url);
|
|
2772
|
+
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
2773
|
+
throw new Error("URL must use http: or https: protocol");
|
|
2774
|
+
}
|
|
2775
|
+
const fetched = await fetchPolicyDocumentText(url);
|
|
2776
|
+
if (!fetched.ok) {
|
|
2777
|
+
return {
|
|
2778
|
+
success: false,
|
|
2779
|
+
error: fetched.error,
|
|
2780
|
+
timing: timer.end()
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
const parsed = parsePolicyDocumentCompat(fetched.text);
|
|
2784
|
+
const data = { ...parsed };
|
|
2785
|
+
if (fetched.warnings && fetched.warnings.length > 0) {
|
|
2786
|
+
data.warnings = [...parsed.warnings ?? [], ...fetched.warnings];
|
|
2787
|
+
}
|
|
2788
|
+
if (data.warnings && data.warnings.length > 0) {
|
|
2789
|
+
data.hints = [
|
|
2790
|
+
"peac.txt is a policy-document surface (docs/specs/PEAC-TXT.md). For key discovery, use /.well-known/peac-issuer.json (docs/specs/PEAC-ISSUER.md)."
|
|
2791
|
+
];
|
|
2792
|
+
}
|
|
2793
|
+
return {
|
|
2794
|
+
success: true,
|
|
2795
|
+
data,
|
|
2796
|
+
timing: timer.end()
|
|
2797
|
+
};
|
|
2798
|
+
} catch (error) {
|
|
2799
|
+
return {
|
|
2800
|
+
...handleError(error),
|
|
2801
|
+
timing: timer.end()
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
};
|
|
2575
2806
|
|
|
2576
2807
|
// src/index.ts
|
|
2577
2808
|
var program = new Command();
|
|
@@ -2709,6 +2940,12 @@ program.addCommand(conformance);
|
|
|
2709
2940
|
program.addCommand(samples);
|
|
2710
2941
|
program.addCommand(reconcileCommand());
|
|
2711
2942
|
doctor(program);
|
|
2943
|
+
program.command("discover").description("Discover and parse a remote /.well-known/peac.txt policy document").argument("<url>", "Origin URL (http/https) whose /.well-known/peac.txt to fetch").option("-j, --json", "output in JSON format").action(async (url, opts) => {
|
|
2944
|
+
const cmd = new DiscoverCommand();
|
|
2945
|
+
const result = await cmd.execute(url, { json: opts.json });
|
|
2946
|
+
console.log(formatOutput(result, opts.json));
|
|
2947
|
+
process.exitCode = result.success ? 0 : 1;
|
|
2948
|
+
});
|
|
2712
2949
|
try {
|
|
2713
2950
|
program.parse();
|
|
2714
2951
|
} catch (err) {
|