prae-cli 0.7.1 → 0.9.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,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import "./evaluate-wMPkjcIC.js";
3
- import "./views-UODslKwu.js";
4
- import { t as main } from "./cli-mahLCjoD.js";
5
- import "./src-Ciggx9ja.js";
3
+ import "./views-CIg2ZNG0.js";
4
+ import { t as main } from "./cli-7yLG89gV.js";
5
+ import "./src-D7g4lvaY.js";
6
6
 
7
7
  //#region src/bin.ts
8
8
  main(process.argv.slice(2)).catch((error) => {
@@ -0,0 +1,99 @@
1
+ import { m as canonical, s as fingerprint } from "./src-D7g4lvaY.js";
2
+ import { createPrivateKey, createPublicKey, sign, verify } from "node:crypto";
3
+
4
+ //#region src/checkpoint.ts
5
+ /** What the signature covers: the facts, never the timestamp or the name of the key. */
6
+ function signable(cp) {
7
+ return Buffer.from(canonical({
8
+ ledger: cp.ledger,
9
+ entries: cp.entries,
10
+ head: cp.head
11
+ }));
12
+ }
13
+ function makeCheckpoint(rows, ledger, privateKeyPem, at = (/* @__PURE__ */ new Date()).toISOString()) {
14
+ const head = rows.at(-1);
15
+ const cp = {
16
+ prae: "checkpoint/1",
17
+ at,
18
+ ledger,
19
+ entries: rows.length,
20
+ head: head === void 0 ? {
21
+ seq: -1,
22
+ hash: ""
23
+ } : {
24
+ seq: head.seq,
25
+ hash: head.hash
26
+ }
27
+ };
28
+ if (privateKeyPem === void 0) return cp;
29
+ const key = createPrivateKey(privateKeyPem);
30
+ const publicKey = createPublicKey(key).export({
31
+ type: "spki",
32
+ format: "pem"
33
+ }).toString();
34
+ return {
35
+ ...cp,
36
+ fingerprint: fingerprint(publicKey),
37
+ sig: sign(null, signable(cp), key).toString("base64")
38
+ };
39
+ }
40
+ /**
41
+ * The ledger as it is now, against the checkpoint as it was then. Rows may
42
+ * have been appended since; nothing before the head may differ.
43
+ */
44
+ function checkCheckpoint(rows, cp, publicKeyPem) {
45
+ const lines = [];
46
+ let ok = true;
47
+ if (cp.prae !== "checkpoint/1") return {
48
+ ok: false,
49
+ lines: [`not a checkpoint: prae is ${String(cp.prae)}`]
50
+ };
51
+ if (publicKeyPem !== void 0) if (cp.sig === void 0) {
52
+ ok = false;
53
+ lines.push("checkpoint is not signed; a key was given to check it against");
54
+ } else {
55
+ let good = false;
56
+ try {
57
+ good = verify(null, signable(cp), createPublicKey(publicKeyPem), Buffer.from(cp.sig, "base64"));
58
+ } catch {
59
+ good = false;
60
+ }
61
+ if (good) lines.push(`checkpoint signature verifies against ${fingerprint(publicKeyPem)}`);
62
+ else {
63
+ ok = false;
64
+ lines.push(`checkpoint signature FAILS against ${fingerprint(publicKeyPem)} — the checkpoint itself has been altered or was signed by another key`);
65
+ }
66
+ }
67
+ if (rows.length < cp.entries) {
68
+ ok = false;
69
+ lines.push(`ledger holds ${rows.length} rows; the checkpoint of ${cp.at} recorded ${cp.entries} — ${cp.entries - rows.length} row${cp.entries - rows.length === 1 ? "" : "s"} removed since`);
70
+ return {
71
+ ok,
72
+ lines
73
+ };
74
+ }
75
+ if (cp.entries === 0) {
76
+ lines.push(`checkpoint of ${cp.at} recorded an empty ledger; ${rows.length} rows since`);
77
+ return {
78
+ ok,
79
+ lines
80
+ };
81
+ }
82
+ const then = rows.find((row) => row.seq === cp.head.seq);
83
+ if (then === void 0 || then.hash !== cp.head.hash) {
84
+ ok = false;
85
+ lines.push(`row ${cp.head.seq} does not match the checkpoint head (${cp.head.hash.slice(0, 16)}…) — the ledger was rewritten since ${cp.at}`);
86
+ return {
87
+ ok,
88
+ lines
89
+ };
90
+ }
91
+ lines.push(`checkpoint of ${cp.at} holds: ${cp.entries} rows then, ${rows.length} now, head ${cp.head.hash.slice(0, 16)}… intact`);
92
+ return {
93
+ ok,
94
+ lines
95
+ };
96
+ }
97
+
98
+ //#endregion
99
+ export { checkCheckpoint, makeCheckpoint };
@@ -1,6 +1,6 @@
1
1
  import { t as evaluate } from "./evaluate-wMPkjcIC.js";
2
- import { a as renderPacks, c as renderNav, d as FRAMEWORKS, f as cite, i as renderOverview, l as renderShell, n as renderApprovals, o as CONTROLS, r as renderCompliance, s as esc, t as navFor, u as VERDICT_LABEL$1 } from "./views-UODslKwu.js";
3
- import { f as chain, i as fingerprint, l as verifySignature, m as seal, n as readEntries, r as verifyEntries, t as Ledger, u as GENESIS } from "./src-Ciggx9ja.js";
2
+ import { a as renderPacks, c as renderNav, d as FRAMEWORKS, f as cite, i as renderOverview, l as renderShell, n as renderApprovals, o as CONTROLS, r as renderCompliance, s as esc, t as navFor, u as VERDICT_LABEL$1 } from "./views-CIg2ZNG0.js";
3
+ import { _ as seal, f as verifySignature, h as chain, n as readEntries, p as GENESIS, r as verifyEntries, s as fingerprint, t as Ledger } from "./src-D7g4lvaY.js";
4
4
  import { existsSync, readFileSync, readdirSync, statSync, watch } from "node:fs";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
6
  import { dirname, extname, join, normalize, resolve, sep } from "node:path";
@@ -972,7 +972,6 @@ function verifyRecord(record, tamper, repair = false) {
972
972
  signatures
973
973
  };
974
974
  }
975
- const OWASP = "owasp-llm-top-10-2026";
976
975
  /**
977
976
  * Evidence toward each control, from the pack and the ledger as they are
978
977
  * now — the rows the controls package defines, so the console, the exporter and
@@ -1001,15 +1000,18 @@ function complianceOf(record) {
1001
1000
  verification: verifyEntries(text),
1002
1001
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1003
1002
  };
1004
- const owasp = FRAMEWORKS[OWASP]?.controls ?? {};
1005
1003
  return { controls: CONTROLS.map((control) => {
1006
- const code = /LLM\d\d/.exec(control.id)?.[0];
1007
- const named = code === void 0 ? void 0 : owasp[code];
1004
+ const found = Object.entries(FRAMEWORKS).find(([, f]) => control.id.startsWith(`${f.short} · `));
1005
+ const code = found === void 0 ? void 0 : control.id.split(" · ")[1];
1006
+ const named = found === void 0 || code === void 0 ? void 0 : found[1].controls[code];
1007
+ const standard = control.standard;
1008
+ const name = named?.name ?? control.name;
1008
1009
  return {
1009
- id: code ?? control.id,
1010
- ...named === void 0 ? {} : { name: named.name },
1010
+ id: code ?? (standard === void 0 ? control.id : control.id.split(" · ")[1] ?? control.id),
1011
+ ...name === void 0 ? {} : { name },
1011
1012
  asks: control.asks,
1012
- framework: named === void 0 ? "obligation" : OWASP,
1013
+ framework: found?.[0] ?? standard?.id ?? "obligation",
1014
+ ...found === void 0 ? standard === void 0 ? {} : { frameworkName: standard.name } : { frameworkName: found[1].name },
1013
1015
  ...control.evidence(data)
1014
1016
  };
1015
1017
  }) };
@@ -1187,14 +1189,42 @@ const USAGE = `⌜▪⌟ prae — operator policy for agent harnesses
1187
1189
  the exit code is non-zero if any invariant no longer holds, so a
1188
1190
  control pack can be defended in CI. --pack overrides the suite's own.
1189
1191
 
1190
- prae verify <ledger.jsonl> [--key <public.pem>]
1192
+ prae verify <ledger.jsonl> [--key <public.pem>] [--checkpoint <file>]
1191
1193
  Re-derive every hash in a decision log and report any break. With
1192
- --key, also verify each row's signature against that pinned key.
1194
+ --key, also verify each row's signature against that pinned key. With
1195
+ --checkpoint, also check that nothing before that checkpoint's head
1196
+ has been removed or rewritten since it was taken.
1197
+
1198
+ prae checkpoint <ledger.jsonl> [--key <private.pem>] [--out <file>]
1199
+ Record the ledger's length and head, signed, to keep somewhere the
1200
+ ledger's machine cannot write. A chain proves no row inside the file
1201
+ was touched; a checkpoint proves the file was not cut at the end.
1202
+
1203
+ prae export <ledger.jsonl> --format ocsf [--out <file>]
1204
+ [--hec <url> --hec-token <token>]
1205
+ The record as OCSF 1.5.0 events (NDJSON) for a SIEM or data lake:
1206
+ refusals, holds and withheld outputs as Detection Findings, allowed
1207
+ calls as API Activity, every event carrying the row's seal. With --hec,
1208
+ post them to a Splunk HTTP Event Collector instead of writing a file.
1193
1209
 
1194
1210
  prae keygen [--out <dir>]
1195
1211
  Generate an Ed25519 keypair for signing ledger rows. Prints the
1196
1212
  fingerprint a counterparty pins.
1197
1213
 
1214
+ prae keygen --role operator|policy|ledger [--out <dir>]
1215
+ A keypair whose private half is sealed under a passphrase (asked for
1216
+ without echo, or PRAE_KEY_PASSPHRASE). The operator key signs answers
1217
+ to held calls; the policy key signs packs. Pin the public half in the
1218
+ gate (operator.publicKey / policyKey); the agent can read the sealed
1219
+ file and still cannot sign. The ledger key is for the signer that
1220
+ holds it on the harness's behalf (signerFd): prae-signing.*.
1221
+
1222
+ prae sign-pack <pack.json> --key <policy.key.sealed.pem>
1223
+ Sign a pack's exact bytes with the sealed policy key (passphrase asked
1224
+ without echo, or PRAE_KEY_PASSPHRASE), writing <pack.json>.sig. A gate
1225
+ with policyKey pinned governs only by a signed pack whose version
1226
+ rises; raise the version before signing.
1227
+
1198
1228
  prae receipt <ledger.jsonl> <seq> --key <private.pem> [--out <file>]
1199
1229
  Export one decision as a self-contained signed receipt.
1200
1230
 
@@ -1270,7 +1300,7 @@ async function check(path) {
1270
1300
  * from the findings, so there is nothing here to be careful about printing.
1271
1301
  */
1272
1302
  async function scanFile(path, packPath, json) {
1273
- const { scan } = await import("./src-Vnr0fcpK.js");
1303
+ const { scan } = await import("./src-CwBEFhww.js");
1274
1304
  let content;
1275
1305
  try {
1276
1306
  content = await readFile(path, "utf8");
@@ -1310,13 +1340,13 @@ async function scanFile(path, packPath, json) {
1310
1340
  * not come up inspecting nothing.
1311
1341
  */
1312
1342
  async function mcp(root, packPath, ledgerPath) {
1313
- const { serveMcp } = await import("./mcp-DDcejhwQ.js");
1343
+ const { serveMcp } = await import("./mcp-doNYU-1C.js");
1314
1344
  const { resolve: resolve$1 } = await import("node:path");
1315
1345
  const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
1316
1346
  const chosen = packPath ?? bundled;
1317
1347
  if (chosen === void 0) return fail("mcp needs --pack <file>; no bundled secret-catcher found");
1318
1348
  const pack = await loadPack(chosen);
1319
- const { Ledger: Ledger$1 } = await import("./src-C9L6NBvu.js");
1349
+ const { Ledger: Ledger$1 } = await import("./src-d82lSKVQ.js");
1320
1350
  const ledger = ledgerPath === void 0 ? void 0 : Ledger$1.open(ledgerPath);
1321
1351
  console.error(`prae mcp: serving ${resolve$1(root)} under ${pack.pack} ${pack.version}` + (ledgerPath === void 0 ? " (no ledger)" : ` · ledger ${ledgerPath}`));
1322
1352
  await serveMcp({
@@ -1368,7 +1398,7 @@ async function explain(path, tool, pairs) {
1368
1398
  * a key that arrived beside it is checked against nothing.
1369
1399
  */
1370
1400
  async function keygen(dir) {
1371
- const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-C9L6NBvu.js");
1401
+ const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
1372
1402
  const { writeFileSync: writeFileSync$1 } = await import("node:fs");
1373
1403
  const keys = generateKeypair();
1374
1404
  const priv = join(dir, "prae-signing.pem");
@@ -1383,8 +1413,73 @@ async function keygen(dir) {
1383
1413
  console.log("receipts, through a channel they trust. Point the plugin at the");
1384
1414
  console.log("private key with `signingKey` to sign every row.");
1385
1415
  }
1416
+ /**
1417
+ * A sealed keypair for a human authority (PRA-106/107): written as
1418
+ * `<role>.pub.pem` and `<role>.key.sealed.pem`. The passphrase is never an
1419
+ * argument — arguments land in process listings and shell history.
1420
+ */
1421
+ async function keygenSealed(role, dir) {
1422
+ if (role !== "operator" && role !== "policy" && role !== "ledger") fail("--role is operator, policy or ledger");
1423
+ const stem = role === "ledger" ? "prae-signing" : role;
1424
+ const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
1425
+ const { writeFileSync: writeFileSync$1 } = await import("node:fs");
1426
+ const keys = sealedKeypair(process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: "));
1427
+ const pub = join(dir, `${stem}.pub.pem`);
1428
+ const sealed = join(dir, `${stem}.key.sealed.pem`);
1429
+ writeFileSync$1(sealed, keys.sealedPrivateKey, { mode: 384 });
1430
+ writeFileSync$1(pub, keys.publicKey);
1431
+ console.log(`sealed ${sealed} (useless without the passphrase)`);
1432
+ console.log(`public ${pub}`);
1433
+ console.log(`fingerprint ${fingerprint$1(keys.publicKey)}`);
1434
+ }
1435
+ /** A detached signature over a pack's bytes (PRA-107). */
1436
+ async function signPack(packPath, keyPath) {
1437
+ const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
1438
+ const { createPublicKey } = await import("node:crypto");
1439
+ const bytes = readFileSync(packPath);
1440
+ const parsed = parsePack(bytes.toString("utf8"));
1441
+ if (!parsed.ok) fail(`${packPath} does not parse; nothing signed`);
1442
+ const passphrase = process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: ");
1443
+ let key;
1444
+ try {
1445
+ key = unsealPrivateKey(readFileSync(keyPath, "utf8"), passphrase);
1446
+ } catch {
1447
+ fail("wrong passphrase, or not a sealed key");
1448
+ }
1449
+ await writeFile(`${packPath}.sig`, `${signBytes(bytes, key)}\n`);
1450
+ const pub = createPublicKey(key).export({
1451
+ type: "spki",
1452
+ format: "pem"
1453
+ });
1454
+ console.log(`signed ${packPath}.sig (${parsed.pack.pack} ${parsed.pack.version})`);
1455
+ console.log(`policy key ${fingerprint$1(pub.toString())}`);
1456
+ }
1457
+ /** Read a line from the terminal without echoing it. */
1458
+ function askHidden(prompt) {
1459
+ const input = process.stdin;
1460
+ if (!input.isTTY) fail("no terminal to ask on: set PRAE_KEY_PASSPHRASE");
1461
+ process.stdout.write(prompt);
1462
+ input.setRawMode(true);
1463
+ input.resume();
1464
+ input.setEncoding("utf8");
1465
+ return new Promise((done) => {
1466
+ let typed = "";
1467
+ const onData = (ch) => {
1468
+ if (ch === "\r" || ch === "\n") {
1469
+ input.setRawMode(false);
1470
+ input.pause();
1471
+ input.off("data", onData);
1472
+ process.stdout.write("\n");
1473
+ done(typed);
1474
+ } else if (ch === "") process.exit(130);
1475
+ else if (ch === "") typed = typed.slice(0, -1);
1476
+ else typed += ch;
1477
+ };
1478
+ input.on("data", onData);
1479
+ });
1480
+ }
1386
1481
  async function receipt(path, seq, keyPath, out) {
1387
- const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-C9L6NBvu.js");
1482
+ const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
1388
1483
  const { readFileSync: readFileSync$1 } = await import("node:fs");
1389
1484
  const { createPublicKey } = await import("node:crypto");
1390
1485
  const privateKey = readFileSync$1(keyPath, "utf8");
@@ -1414,7 +1509,7 @@ async function receipt(path, seq, keyPath, out) {
1414
1509
  * file and sets the exit code, so scripts can branch on it.
1415
1510
  */
1416
1511
  async function verifyReceiptCmd(path) {
1417
- const { checkReceipt } = await import("./receipt-check-DrDE3Wwi.js");
1512
+ const { checkReceipt } = await import("./receipt-check-CD-W1wXs.js");
1418
1513
  let parsed;
1419
1514
  try {
1420
1515
  parsed = JSON.parse(await readFile(path, "utf8"));
@@ -1445,7 +1540,7 @@ async function test(suitePath, packOverride, asJson) {
1445
1540
  } catch (error) {
1446
1541
  return fail(`${suitePath} is not valid JSON: ${error instanceof Error ? error.message : error}`);
1447
1542
  }
1448
- const { parseSuite, renderSuite, runSuite } = await import("./test-run-DOaCKD1t.js");
1543
+ const { parseSuite, renderSuite, runSuite } = await import("./test-run-XNIRHKOu.js");
1449
1544
  const suite = parseSuite(parsed);
1450
1545
  if (!suite.ok) {
1451
1546
  console.error(`prae: ${suitePath} is not a valid test suite`);
@@ -1456,7 +1551,7 @@ async function test(suitePath, packOverride, asJson) {
1456
1551
  if (chosen === void 0) return fail("test needs a pack: set `pack` in the suite or pass --pack");
1457
1552
  const pack = await loadPack(chosen);
1458
1553
  const { capabilityForTool } = await import("capmark");
1459
- const { scan } = await import("./src-Vnr0fcpK.js");
1554
+ const { scan } = await import("./src-CwBEFhww.js");
1460
1555
  const inspect = pack.rules.some((rule) => rule.when.secrets !== void 0) ? (path) => {
1461
1556
  try {
1462
1557
  return scan(readFileSync(path, "utf8"));
@@ -1472,7 +1567,7 @@ async function test(suitePath, packOverride, asJson) {
1472
1567
  else for (const line of renderSuite(result)) console.log(line);
1473
1568
  if (!result.ok) process.exit(1);
1474
1569
  }
1475
- async function verify(path, keyPath) {
1570
+ async function verify(path, keyPath, checkpointPath) {
1476
1571
  let ledger;
1477
1572
  try {
1478
1573
  ledger = Ledger.open(path);
@@ -1484,8 +1579,15 @@ async function verify(path, keyPath) {
1484
1579
  const entries = ledger.read();
1485
1580
  const last = entries.at(-1);
1486
1581
  console.log(`${result.entries} entries · chain intact${last === void 0 ? "" : ` · seal ${seal(last)}`}`);
1582
+ if (checkpointPath !== void 0) {
1583
+ const { checkCheckpoint } = await import("./checkpoint-BWSuhZ5h.js");
1584
+ const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
1585
+ const verdict = checkCheckpoint(entries, JSON.parse(readFileSync(checkpointPath, "utf8")), publicKey);
1586
+ for (const line of verdict.lines) (verdict.ok ? console.log : console.error)(`checkpoint: ${line}`);
1587
+ if (!verdict.ok) process.exitCode = 1;
1588
+ }
1487
1589
  if (keyPath !== void 0) {
1488
- const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-C9L6NBvu.js");
1590
+ const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
1489
1591
  const { readFileSync: readFileSync$1 } = await import("node:fs");
1490
1592
  const publicKey = readFileSync$1(keyPath, "utf8");
1491
1593
  let signed = 0;
@@ -1563,7 +1665,7 @@ async function report(path, out, packPath) {
1563
1665
  * that step is a proposal a human approves, and it starts from this list.
1564
1666
  */
1565
1667
  async function instructions(from, asJson) {
1566
- const { ingest } = await import("./src-CraUX64d.js");
1668
+ const { ingest } = await import("./src-d4c_n6wd.js");
1567
1669
  const result = ingest(from);
1568
1670
  if (asJson) {
1569
1671
  console.log(JSON.stringify(result, null, 2));
@@ -1599,7 +1701,7 @@ async function instructions(from, asJson) {
1599
1701
  * on purpose rather than something that happened to their policy.
1600
1702
  */
1601
1703
  async function propose(from, options) {
1602
- const [{ ingest }, { extract }] = await Promise.all([import("./src-CraUX64d.js"), import("./src-CQ0UmFUK.js")]);
1704
+ const [{ ingest }, { extract }] = await Promise.all([import("./src-d4c_n6wd.js"), import("./src-CBGZVvt7.js")]);
1603
1705
  const read = ingest(from);
1604
1706
  if (read.files.length === 0) {
1605
1707
  console.log(`No instruction files found at or above ${from}.`);
@@ -1659,9 +1761,9 @@ async function consoleOut(path, argv) {
1659
1761
  const instructionsDir = flag(argv, "instructions");
1660
1762
  if (instructionsDir !== void 0) {
1661
1763
  const [{ ingest }, { extract }, { renderInstructions }] = await Promise.all([
1662
- import("./src-CraUX64d.js"),
1663
- import("./src-CQ0UmFUK.js"),
1664
- import("./instructions-C104EUaE.js")
1764
+ import("./src-d4c_n6wd.js"),
1765
+ import("./src-CBGZVvt7.js"),
1766
+ import("./instructions-Bg5puOen.js")
1665
1767
  ]);
1666
1768
  const read = ingest(instructionsDir);
1667
1769
  instructionsView = renderInstructions(data, read, extract(read.statements));
@@ -1759,6 +1861,60 @@ async function init(argv) {
1759
1861
  return fail(error instanceof Error ? error.message : String(error));
1760
1862
  }
1761
1863
  }
1864
+ async function checkpoint(path, keyPath, out) {
1865
+ const { makeCheckpoint } = await import("./checkpoint-BWSuhZ5h.js");
1866
+ let ledger;
1867
+ try {
1868
+ ledger = Ledger.open(path);
1869
+ } catch (error) {
1870
+ return fail(error instanceof Error ? error.message : String(error));
1871
+ }
1872
+ const chain$1 = ledger.verify();
1873
+ if (!chain$1.ok) return fail(`chain broken at line ${chain$1.problems[0]?.line}: ${chain$1.problems[0]?.message} — a checkpoint of a broken chain would certify the break`);
1874
+ const privateKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
1875
+ const cp = makeCheckpoint(ledger.read(), path.split("/").at(-1) ?? path, privateKey);
1876
+ const text = `${JSON.stringify(cp, null, 2)}\n`;
1877
+ if (out === void 0) console.log(text);
1878
+ else {
1879
+ await mkdir(dirname(out), { recursive: true });
1880
+ await writeFile(out, text);
1881
+ console.log(`checkpoint ${out} · ${cp.entries} rows · head ${cp.head.hash.slice(0, 16)}…${cp.sig === void 0 ? " · unsigned" : ` · signed ${cp.fingerprint}`}`);
1882
+ console.log("Keep it where this machine cannot write. `prae verify --checkpoint` later detects rows removed or rewritten since.");
1883
+ }
1884
+ }
1885
+ async function exportLedger(path, options) {
1886
+ const { toOcsf, ndjson, sendToHec } = await import("./ocsf-DhVGbBUw.js");
1887
+ const { createRequire } = await import("node:module");
1888
+ const { version } = createRequire(import.meta.url)("../package.json");
1889
+ let ledger;
1890
+ try {
1891
+ ledger = Ledger.open(path);
1892
+ } catch (error) {
1893
+ return fail(error instanceof Error ? error.message : String(error));
1894
+ }
1895
+ const events = toOcsf(ledger.read(), {
1896
+ product: {
1897
+ name: "prae-gate",
1898
+ version
1899
+ },
1900
+ ledger: path
1901
+ });
1902
+ if (options.hec !== void 0) {
1903
+ if (options.token === void 0) fail("--hec needs --hec-token");
1904
+ const sent = await sendToHec(events, options.hec, options.token);
1905
+ if (sent.status < 200 || sent.status >= 300) return fail(`HEC answered ${sent.status}: ${sent.body.slice(0, 200)}`);
1906
+ console.log(`${sent.sent} events → ${options.hec} (${sent.status})`);
1907
+ return;
1908
+ }
1909
+ const text = ndjson(events);
1910
+ if (options.out === void 0) {
1911
+ process.stdout.write(text);
1912
+ return;
1913
+ }
1914
+ await mkdir(dirname(options.out), { recursive: true });
1915
+ await writeFile(options.out, text);
1916
+ console.log(`${events.length} OCSF events → ${options.out} · seals carried under unmapped; the signature covers the ledger row, not the event`);
1917
+ }
1762
1918
  async function main(argv) {
1763
1919
  const [command, ...rest] = argv;
1764
1920
  switch (command) {
@@ -1782,7 +1938,17 @@ async function main(argv) {
1782
1938
  if (root === void 0) fail("mcp needs --root <dir>; serving without a boundary is not a gate");
1783
1939
  return mcp(root, flag(rest, "pack"), flag(rest, "ledger"));
1784
1940
  }
1785
- case "keygen": return keygen(flag(rest, "out") ?? ".");
1941
+ case "keygen": {
1942
+ const role = flag(rest, "role");
1943
+ if (role !== void 0) return keygenSealed(role, flag(rest, "out") ?? ".");
1944
+ return keygen(flag(rest, "out") ?? ".");
1945
+ }
1946
+ case "sign-pack": {
1947
+ const [path] = rest;
1948
+ const key = flag(rest, "key");
1949
+ if (path === void 0 || key === void 0) fail("sign-pack needs <pack.json> --key <policy.key.sealed.pem>");
1950
+ return signPack(path, key);
1951
+ }
1786
1952
  case "receipt": {
1787
1953
  const [path, seq] = rest;
1788
1954
  const key = flag(rest, "key");
@@ -1798,7 +1964,23 @@ async function main(argv) {
1798
1964
  case "verify": {
1799
1965
  const path = rest[0];
1800
1966
  if (path === void 0) fail("verify needs a ledger file");
1801
- return verify(path, flag(rest, "key"));
1967
+ return verify(path, flag(rest, "key"), flag(rest, "checkpoint"));
1968
+ }
1969
+ case "checkpoint": {
1970
+ const path = rest[0];
1971
+ if (path === void 0) fail("checkpoint needs a ledger file");
1972
+ return checkpoint(path, flag(rest, "key"), flag(rest, "out"));
1973
+ }
1974
+ case "export": {
1975
+ const path = rest[0];
1976
+ if (path === void 0) fail("export needs a ledger file");
1977
+ const format = flag(rest, "format") ?? "ocsf";
1978
+ if (format !== "ocsf") fail(`unknown format ${format}; only ocsf today`);
1979
+ return exportLedger(path, {
1980
+ out: flag(rest, "out"),
1981
+ hec: flag(rest, "hec"),
1982
+ token: flag(rest, "hec-token")
1983
+ });
1802
1984
  }
1803
1985
  case "verify-receipt": {
1804
1986
  const path = rest[0];
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./evaluate-wMPkjcIC.js";
2
- import "./views-UODslKwu.js";
3
- import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-mahLCjoD.js";
4
- import "./src-Ciggx9ja.js";
2
+ import "./views-CIg2ZNG0.js";
3
+ import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-7yLG89gV.js";
4
+ import "./src-D7g4lvaY.js";
5
5
 
6
6
  export { applyWrite, defaultProfile, dshHome, main, planWrite, praeRow, profileAt };
@@ -1,4 +1,4 @@
1
- import { c as renderNav, l as renderShell, s as esc, t as navFor } from "./views-UODslKwu.js";
1
+ import { c as renderNav, l as renderShell, s as esc, t as navFor } from "./views-CIg2ZNG0.js";
2
2
 
3
3
  //#region src/console/instructions.ts
4
4
  const STYLE = `
@@ -1,5 +1,5 @@
1
1
  import { t as evaluate } from "./evaluate-wMPkjcIC.js";
2
- import { a as scan, n as MAX_SCAN_BYTES } from "./src-Br1tGPtS.js";
2
+ import { a as scan, n as MAX_SCAN_BYTES } from "./src-B25SHWcz.js";
3
3
  import { readFile, readdir, stat } from "node:fs/promises";
4
4
  import { resolve, sep } from "node:path";
5
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";