prae-cli 0.4.0 → 0.5.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/lib/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "./evaluate-BilZez15.js";
3
- import { t as main } from "./cli-BvAYSK5w.js";
4
- import "./src-CAUNqZJ1.js";
2
+ import "./evaluate-DMyL3qM_.js";
3
+ import { t as main } from "./cli-DfXHsQO7.js";
4
+ import "./src-CXU0_W1P.js";
5
5
  import "./views-DalPkBZZ.js";
6
6
 
7
7
  //#region src/bin.ts
@@ -1,5 +1,5 @@
1
- import { t as evaluate } from "./evaluate-BilZez15.js";
2
- import { p as seal, t as Ledger } from "./src-CAUNqZJ1.js";
1
+ import { t as evaluate } from "./evaluate-DMyL3qM_.js";
2
+ import { m as seal, t as Ledger } from "./src-CXU0_W1P.js";
3
3
  import { a as renderPacks, c as renderShell, i as renderOverview, l as VERDICT_LABEL$1, n as renderApprovals, o as esc, r as renderCompliance, s as renderNav, t as navFor } from "./views-DalPkBZZ.js";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -864,6 +864,10 @@ const USAGE = `prae — operator policy for agent harnesses
864
864
  prae receipt <ledger.jsonl> <seq> --key <private.pem> [--out <file>]
865
865
  Export one decision as a self-contained signed receipt.
866
866
 
867
+ prae verify-receipt <receipt.json>
868
+ Check one receipt with nothing but the file: recompute its hash, verify
869
+ its signature, and print the key fingerprint to compare against your own.
870
+
867
871
  prae report <ledger.jsonl> [--out <file.html>]
868
872
  Render the decision record as a printable evidence report.
869
873
 
@@ -966,13 +970,13 @@ async function scanFile(path, packPath, json) {
966
970
  * not come up inspecting nothing.
967
971
  */
968
972
  async function mcp(root, packPath, ledgerPath) {
969
- const { serveMcp } = await import("./mcp-BH5I4Eyu.js");
973
+ const { serveMcp } = await import("./mcp-CEN48TpN.js");
970
974
  const { resolve: resolve$1 } = await import("node:path");
971
975
  const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
972
976
  const chosen = packPath ?? bundled;
973
977
  if (chosen === void 0) return fail("mcp needs --pack <file>; no bundled secret-catcher found");
974
978
  const pack = await loadPack(chosen);
975
- const { Ledger: Ledger$1 } = await import("./src-CBK_quVO.js");
979
+ const { Ledger: Ledger$1 } = await import("./src-Kubj3MTX.js");
976
980
  const ledger = ledgerPath === void 0 ? void 0 : Ledger$1.open(ledgerPath);
977
981
  console.error(`prae mcp: serving ${resolve$1(root)} under ${pack.pack} ${pack.version}` + (ledgerPath === void 0 ? " (no ledger)" : ` · ledger ${ledgerPath}`));
978
982
  await serveMcp({
@@ -1024,7 +1028,7 @@ async function explain(path, tool, pairs) {
1024
1028
  * a key that arrived beside it is checked against nothing.
1025
1029
  */
1026
1030
  async function keygen(dir) {
1027
- const { generateKeypair, fingerprint } = await import("./src-CBK_quVO.js");
1031
+ const { generateKeypair, fingerprint } = await import("./src-Kubj3MTX.js");
1028
1032
  const { writeFileSync: writeFileSync$1 } = await import("node:fs");
1029
1033
  const keys = generateKeypair();
1030
1034
  const priv = join(dir, "prae-signing.pem");
@@ -1040,7 +1044,7 @@ async function keygen(dir) {
1040
1044
  console.log("private key with `signingKey` to sign every row.");
1041
1045
  }
1042
1046
  async function receipt(path, seq, keyPath, out) {
1043
- const { Ledger: Ledger$1, makeReceipt, fingerprint } = await import("./src-CBK_quVO.js");
1047
+ const { Ledger: Ledger$1, makeReceipt, fingerprint } = await import("./src-Kubj3MTX.js");
1044
1048
  const { readFileSync: readFileSync$1 } = await import("node:fs");
1045
1049
  const { createPublicKey } = await import("node:crypto");
1046
1050
  const privateKey = readFileSync$1(keyPath, "utf8");
@@ -1061,6 +1065,27 @@ async function receipt(path, seq, keyPath, out) {
1061
1065
  console.log("The verifier compares that fingerprint against the one they");
1062
1066
  console.log("pinned — a receipt is only as good as the key they trust.");
1063
1067
  }
1068
+ /**
1069
+ * `prae verify-receipt`: check a single self-contained receipt.
1070
+ *
1071
+ * The counterparty's command. It needs nothing but the receipt file — no
1072
+ * ledger, no PRAE account, no trust in the machine that issued it. The verdict
1073
+ * and its wording live in `receipt-check.ts` as a pure function; this reads the
1074
+ * file and sets the exit code, so scripts can branch on it.
1075
+ */
1076
+ async function verifyReceiptCmd(path) {
1077
+ const { checkReceipt } = await import("./receipt-check-Dql0uqxw.js");
1078
+ let parsed;
1079
+ try {
1080
+ parsed = JSON.parse(await readFile(path, "utf8"));
1081
+ } catch {
1082
+ return fail(`cannot read a receipt from ${path}`);
1083
+ }
1084
+ const result = checkReceipt(parsed);
1085
+ for (const line of result.lines) if (result.ok) console.log(line);
1086
+ else console.error(line);
1087
+ if (!result.ok) process.exit(1);
1088
+ }
1064
1089
  async function verify(path, keyPath) {
1065
1090
  let ledger;
1066
1091
  try {
@@ -1074,7 +1099,7 @@ async function verify(path, keyPath) {
1074
1099
  const last = entries.at(-1);
1075
1100
  console.log(`${result.entries} entries · chain intact${last === void 0 ? "" : ` · seal ${seal(last)}`}`);
1076
1101
  if (keyPath !== void 0) {
1077
- const { verifySignature, fingerprint } = await import("./src-CBK_quVO.js");
1102
+ const { verifySignature, fingerprint } = await import("./src-Kubj3MTX.js");
1078
1103
  const { readFileSync: readFileSync$1 } = await import("node:fs");
1079
1104
  const publicKey = readFileSync$1(keyPath, "utf8");
1080
1105
  let signed = 0;
@@ -1150,7 +1175,7 @@ async function report(path, out) {
1150
1175
  * that step is a proposal a human approves, and it starts from this list.
1151
1176
  */
1152
1177
  async function instructions(from, asJson) {
1153
- const { ingest } = await import("./src-CCeafjdm.js");
1178
+ const { ingest } = await import("./src-C4fnUm9V.js");
1154
1179
  const result = ingest(from);
1155
1180
  if (asJson) {
1156
1181
  console.log(JSON.stringify(result, null, 2));
@@ -1186,7 +1211,7 @@ async function instructions(from, asJson) {
1186
1211
  * on purpose rather than something that happened to their policy.
1187
1212
  */
1188
1213
  async function propose(from, options) {
1189
- const [{ ingest }, { extract }] = await Promise.all([import("./src-CCeafjdm.js"), import("./src-aU5WW02C.js")]);
1214
+ const [{ ingest }, { extract }] = await Promise.all([import("./src-C4fnUm9V.js"), import("./src-BfYsUDW9.js")]);
1190
1215
  const read = ingest(from);
1191
1216
  if (read.files.length === 0) {
1192
1217
  console.log(`No instruction files found at or above ${from}.`);
@@ -1246,8 +1271,8 @@ async function consoleOut(path, argv) {
1246
1271
  const instructionsDir = flag(argv, "instructions");
1247
1272
  if (instructionsDir !== void 0) {
1248
1273
  const [{ ingest }, { extract }, { renderInstructions }] = await Promise.all([
1249
- import("./src-CCeafjdm.js"),
1250
- import("./src-aU5WW02C.js"),
1274
+ import("./src-C4fnUm9V.js"),
1275
+ import("./src-BfYsUDW9.js"),
1251
1276
  import("./instructions-D4VOn2lk.js")
1252
1277
  ]);
1253
1278
  const read = ingest(instructionsDir);
@@ -1369,6 +1394,11 @@ async function main(argv) {
1369
1394
  if (path === void 0) fail("verify needs a ledger file");
1370
1395
  return verify(path, flag(rest, "key"));
1371
1396
  }
1397
+ case "verify-receipt": {
1398
+ const path = rest[0];
1399
+ if (path === void 0) fail("verify-receipt needs a receipt file");
1400
+ return verifyReceiptCmd(path);
1401
+ }
1372
1402
  case "report": {
1373
1403
  const path = rest[0];
1374
1404
  if (path === void 0) fail("report needs a ledger file");
@@ -266,6 +266,42 @@ function hostIn(host, list$1) {
266
266
  return host === want;
267
267
  });
268
268
  }
269
+ /**
270
+ * Argument names that conventionally carry a filesystem path, in the order
271
+ * they are tried when a path rule names none.
272
+ *
273
+ * A path rule used to default to the single argument spelled `path`. DeepSeek
274
+ * Harness's own `read`, `write` and `edit` tools pass `file_path`, so every
275
+ * shipped path rule without an explicit `arg` — including the baseline's
276
+ * "deny read ./.env" — matched nothing on the very harness it was written
277
+ * for, while the ledger dutifully recorded every allowed read. A rule that
278
+ * silently never fires is the worst way for policy to be wrong; defaulting
279
+ * to the conventional set is the same call the dsh adapter already made for
280
+ * path resolution, now made once, here, for matching too.
281
+ */
282
+ const PATH_ARGS = [
283
+ "path",
284
+ "file_path",
285
+ "filePath",
286
+ "target",
287
+ "directory",
288
+ "cwd",
289
+ "file",
290
+ "dest",
291
+ "destination",
292
+ "source"
293
+ ];
294
+ /** The first conventional path argument present, as a string, or undefined. */
295
+ function pathArgOf(args, explicit) {
296
+ if (explicit !== void 0) {
297
+ const v = args[explicit];
298
+ return typeof v === "string" ? v : void 0;
299
+ }
300
+ for (const key of PATH_ARGS) {
301
+ const v = args[key];
302
+ if (typeof v === "string") return v;
303
+ }
304
+ }
269
305
 
270
306
  //#endregion
271
307
  //#region ../kernel/src/rule.ts
@@ -319,7 +355,7 @@ function matches(when, facts) {
319
355
  if (!commandMatches(when.command, raw)) return false;
320
356
  }
321
357
  if (when.path !== void 0) {
322
- const raw = facts.args[when.path.arg ?? "path"];
358
+ const raw = pathArgOf(facts.args, when.path.arg);
323
359
  if (typeof raw !== "string") return false;
324
360
  if (when.path.inside !== void 0 && !withinAny(raw, when.path.inside)) return false;
325
361
  if (when.path.outside !== void 0 && withinAny(raw, when.path.outside)) return false;
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import "./evaluate-BilZez15.js";
2
- import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-BvAYSK5w.js";
3
- import "./src-CAUNqZJ1.js";
1
+ import "./evaluate-DMyL3qM_.js";
2
+ import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-DfXHsQO7.js";
3
+ import "./src-CXU0_W1P.js";
4
4
  import "./views-DalPkBZZ.js";
5
5
 
6
6
  export { applyWrite, defaultProfile, dshHome, main, planWrite, praeRow, profileAt };
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "./evaluate-BilZez15.js";
1
+ import { t as evaluate } from "./evaluate-DMyL3qM_.js";
2
2
  import { a as scan, n as MAX_SCAN_BYTES } from "./src-Br1tGPtS.js";
3
3
  import { readFile, readdir, stat } from "node:fs/promises";
4
4
  import { resolve, sep } from "node:path";
package/lib/playground.js CHANGED
@@ -269,6 +269,42 @@
269
269
  return host === want;
270
270
  });
271
271
  }
272
+ /**
273
+ * Argument names that conventionally carry a filesystem path, in the order
274
+ * they are tried when a path rule names none.
275
+ *
276
+ * A path rule used to default to the single argument spelled `path`. DeepSeek
277
+ * Harness's own `read`, `write` and `edit` tools pass `file_path`, so every
278
+ * shipped path rule without an explicit `arg` — including the baseline's
279
+ * "deny read ./.env" — matched nothing on the very harness it was written
280
+ * for, while the ledger dutifully recorded every allowed read. A rule that
281
+ * silently never fires is the worst way for policy to be wrong; defaulting
282
+ * to the conventional set is the same call the dsh adapter already made for
283
+ * path resolution, now made once, here, for matching too.
284
+ */
285
+ const PATH_ARGS = [
286
+ "path",
287
+ "file_path",
288
+ "filePath",
289
+ "target",
290
+ "directory",
291
+ "cwd",
292
+ "file",
293
+ "dest",
294
+ "destination",
295
+ "source"
296
+ ];
297
+ /** The first conventional path argument present, as a string, or undefined. */
298
+ function pathArgOf(args, explicit) {
299
+ if (explicit !== void 0) {
300
+ const v = args[explicit];
301
+ return typeof v === "string" ? v : void 0;
302
+ }
303
+ for (const key of PATH_ARGS) {
304
+ const v = args[key];
305
+ if (typeof v === "string") return v;
306
+ }
307
+ }
272
308
 
273
309
  //#endregion
274
310
  //#region ../kernel/src/rule.ts
@@ -322,7 +358,7 @@
322
358
  if (!commandMatches(when.command, raw)) return false;
323
359
  }
324
360
  if (when.path !== void 0) {
325
- const raw = facts.args[when.path.arg ?? "path"];
361
+ const raw = pathArgOf(facts.args, when.path.arg);
326
362
  if (typeof raw !== "string") return false;
327
363
  if (when.path.inside !== void 0 && !withinAny(raw, when.path.inside)) return false;
328
364
  if (when.path.outside !== void 0 && withinAny(raw, when.path.outside)) return false;
@@ -0,0 +1,39 @@
1
+ import { c as verifyReceipt, i as fingerprint } from "./src-CXU0_W1P.js";
2
+
3
+ //#region src/receipt-check.ts
4
+ /** Shape we require before we will call it a receipt at all. */
5
+ function looksLikeReceipt(value) {
6
+ if (typeof value !== "object" || value === null) return false;
7
+ const r = value;
8
+ return typeof r.signature === "string" && typeof r.publicKey === "string" && typeof r.entry === "object" && r.entry !== null;
9
+ }
10
+ function checkReceipt(parsed) {
11
+ if (!looksLikeReceipt(parsed)) return {
12
+ ok: false,
13
+ lines: ["✗ not a receipt: expected `entry`, `signature` and `publicKey` fields"]
14
+ };
15
+ const result = verifyReceipt(parsed);
16
+ if (!result.ok) return {
17
+ ok: false,
18
+ lines: [`✗ receipt does NOT verify: ${result.reason}`]
19
+ };
20
+ const { verdict, tool, rule, subject } = parsed.entry;
21
+ return {
22
+ ok: true,
23
+ lines: [
24
+ "✓ receipt verifies — authentic and unaltered",
25
+ ` ${[
26
+ verdict,
27
+ tool,
28
+ rule === void 0 ? void 0 : `· ${rule}`,
29
+ subject === void 0 ? void 0 : `· ${subject}`
30
+ ].filter((part) => part !== void 0).join(" ")}`,
31
+ ` sealed by key ${fingerprint(parsed.publicKey)}`,
32
+ " Compare that fingerprint to the one you pinned out of band.",
33
+ " It matches → this is a decision that key’s holder sealed, unchanged."
34
+ ]
35
+ };
36
+ }
37
+
38
+ //#endregion
39
+ export { checkReceipt };
@@ -76,6 +76,23 @@ function makeReceipt(entry, privateKeyPem, publicKeyPem) {
76
76
  publicKey: publicKeyPem
77
77
  };
78
78
  }
79
+ /** Verify a receipt from nothing but the receipt. */
80
+ function verifyReceipt(receipt) {
81
+ if (receipt.receipt !== "prae/1") return {
82
+ ok: false,
83
+ reason: `unknown receipt format ${String(receipt.receipt)}`
84
+ };
85
+ const { seq, prev, hash, sig,...record } = receipt.entry;
86
+ if (hashEntry(record, seq, prev) !== hash) return {
87
+ ok: false,
88
+ reason: "entry contents do not match their seal; the row was altered"
89
+ };
90
+ if (!verifySignature(receipt.entry, receipt.signature, receipt.publicKey)) return {
91
+ ok: false,
92
+ reason: "signature does not verify; not sealed by this key"
93
+ };
94
+ return { ok: true };
95
+ }
79
96
  /**
80
97
  * A short identifier for a public key, for pinning by eye.
81
98
  *
@@ -222,4 +239,4 @@ var Ledger = class Ledger {
222
239
  };
223
240
 
224
241
  //#endregion
225
- export { generateKeypair as a, verifySignature as c, chain as d, hashEntry as f, fingerprint as i, GENESIS as l, readEntries as n, makeReceipt as o, seal as p, verifyEntries as r, signEntry as s, Ledger as t, canonical as u };
242
+ export { generateKeypair as a, verifyReceipt as c, canonical as d, chain as f, fingerprint as i, verifySignature as l, seal as m, readEntries as n, makeReceipt as o, hashEntry as p, verifyEntries as r, signEntry as s, Ledger as t, GENESIS as u };
@@ -0,0 +1,3 @@
1
+ import { a as generateKeypair, c as verifyReceipt, d as canonical, f as chain, i as fingerprint, l as verifySignature, m as seal, n as readEntries, o as makeReceipt, p as hashEntry, r as verifyEntries, s as signEntry, t as Ledger, u as GENESIS } from "./src-CXU0_W1P.js";
2
+
3
+ export { GENESIS, Ledger, canonical, chain, fingerprint, generateKeypair, hashEntry, makeReceipt, readEntries, seal, signEntry, verifyEntries, verifyReceipt, verifySignature };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prae-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Operator policy for agent harnesses: validate packs, explain a decision, verify a decision log, and provision a governed DSH profile.",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -40,8 +40,8 @@
40
40
  "tsdown": "^0.15.6",
41
41
  "typescript": "^5.9.2",
42
42
  "vitest": "^3.2.4",
43
- "@prae/controls": "0.1.0",
44
43
  "@prae/extract": "0.1.0",
44
+ "@prae/controls": "0.1.0",
45
45
  "@prae/inspect": "0.1.0",
46
46
  "@prae/instructions": "0.1.0",
47
47
  "@prae/kernel": "0.1.0",
@@ -1,3 +0,0 @@
1
- import { a as generateKeypair, c as verifySignature, d as chain, f as hashEntry, i as fingerprint, l as GENESIS, n as readEntries, o as makeReceipt, p as seal, r as verifyEntries, s as signEntry, t as Ledger, u as canonical } from "./src-CAUNqZJ1.js";
2
-
3
- export { GENESIS, Ledger, canonical, chain, fingerprint, generateKeypair, hashEntry, makeReceipt, readEntries, seal, signEntry, verifyEntries, verifySignature };
File without changes
File without changes