prae-cli 0.10.0 → 0.10.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
2
  //#region src/bin.ts
3
3
  const argv = process.argv.slice(2);
4
- (argv[0] === "hook" && argv[1] === "claude-code" ? import("./hook-Gll2spTP.js").then(({ runHook }) => runHook(argv.slice(2))) : import("./cli-BcAJlB9B.js").then(({ main }) => main(argv))).catch((error) => {
4
+ (argv[0] === "hook" && argv[1] === "claude-code" ? import("./hook-DxLgNtc4.js").then(({ runHook }) => runHook(argv.slice(2))) : import("./cli-BlWTUnGJ.js").then(({ main }) => main(argv))).catch((error) => {
5
5
  console.error(`prae: ${error instanceof Error ? error.message : error}`);
6
6
  process.exit(1);
7
7
  });
@@ -1,4 +1,4 @@
1
- import { _ as canonical, u as fingerprint } from "./src-Cy-Acrgj.js";
1
+ import { _ as canonical, u as fingerprint } from "./src-CuiWC80o.js";
2
2
  import { createPrivateKey, createPublicKey, sign, verify } from "node:crypto";
3
3
 
4
4
  //#region src/checkpoint.ts
@@ -0,0 +1,6 @@
1
+ import "./src-CZYvvEn5.js";
2
+ import "./src-CuiWC80o.js";
3
+ import { t as main } from "./cli-C_6dosaw.js";
4
+ import "./views-C7DD-Oif.js";
5
+
6
+ export { main };
@@ -1,8 +1,6 @@
1
- import { a as withinAny, r as hostOf, t as evaluate } from "./evaluate-DhoGQw0Z.js";
2
- import { n as cite, t as FRAMEWORKS } from "./frameworks-BgZHGKiH.js";
3
- import { t as parsePack } from "./pack-h5n43S0F.js";
4
- import { a as verifyEntries, b as seal, g as GENESIS, h as verifySignature, l as verifyBytes, n as Ledger, r as readEntries, t as verifyGrowing, u as fingerprint, v as chain } from "./src-Cy-Acrgj.js";
5
- import { a as renderPacks, c as renderNav, 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-CUcixte0.js";
1
+ import { c as withinAny, i as evaluate, n as FRAMEWORKS, o as hostOf, r as cite, t as parsePack } from "./src-CZYvvEn5.js";
2
+ import { a as verifyEntries, b as seal, g as GENESIS, h as verifySignature, l as verifyBytes, n as Ledger, r as readEntries, t as verifyGrowing, u as fingerprint, v as chain } from "./src-CuiWC80o.js";
3
+ import { a as renderPacks, c as renderNav, 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-C7DD-Oif.js";
6
4
  import { createRequire } from "node:module";
7
5
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
8
6
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -866,6 +864,327 @@ function readRedteam(dir, files) {
866
864
  };
867
865
  }
868
866
 
867
+ //#endregion
868
+ //#region ../controls/src/inspect.ts
869
+ /**
870
+ * How a rule is evaluated, one label per matcher field it uses. Every one
871
+ * of them runs in the PRAE kernel's deterministic matcher; these are the
872
+ * only engines the rule model has. There is no semantic model and no
873
+ * syntax tree behind any rule: `command` is a lexical decomposition.
874
+ */
875
+ const ENGINES = {
876
+ tool: "Tool / capability match",
877
+ capability: "Tool / capability match",
878
+ plugin: "Tool / capability match",
879
+ agent: "Agent match",
880
+ path: "Path glob match",
881
+ host: "Host list match",
882
+ arg: "Argument value match",
883
+ count: "Session call count",
884
+ command: "Command decomposition (lexical)",
885
+ secrets: "Content inspection (regex + entropy)"
886
+ };
887
+ function enginesOf(rule) {
888
+ const keys = Object.keys(rule.when);
889
+ if (keys.length === 0) return ["Every call (no matcher)"];
890
+ return [...new Set(keys.map((k) => ENGINES[k] ?? `Unknown matcher: ${k}`))];
891
+ }
892
+ const list = (v) => v === void 0 ? [] : typeof v === "string" ? [v] : v;
893
+ const quoted = (v) => v.map((x) => `\`${String(x)}\``).join(", ");
894
+ /** The plain-language reading, or undefined when it cannot be given reliably. */
895
+ function logicOf(rule) {
896
+ const w = rule.when;
897
+ if (Object.keys(w).some((k) => ENGINES[k] === void 0)) return void 0;
898
+ const subjects = [];
899
+ if (w.capability !== void 0) subjects.push(`calls with capability ${quoted(list(w.capability))}`);
900
+ if (w.tool !== void 0) subjects.push(`calls to ${quoted(list(w.tool))}`);
901
+ if (w.plugin !== void 0) subjects.push(`tools registered by ${quoted(list(w.plugin))}`);
902
+ const evaluates = subjects.length === 0 ? "Every tool call" : `${subjects.join(" that are also ")}`.replace(/^./, (c$1) => c$1.toUpperCase());
903
+ const targets = [];
904
+ if (w.path !== void 0) targets.push(w.path.arg === void 0 ? "the path argument (the first conventional one present)" : `the path in argument \`${w.path.arg}\``);
905
+ if (w.host !== void 0) targets.push(w.host.arg === void 0 ? "the URL argument's host" : `the host of argument \`${w.host.arg}\``);
906
+ if (w.command !== void 0) targets.push("the shell command line, read lexically");
907
+ if (w.arg !== void 0) targets.push(`argument${w.arg.length === 1 ? "" : "s"} ${quoted(w.arg.map((a) => a.name))}`);
908
+ if (w.count !== void 0) targets.push("this session's count of calls with the same capability");
909
+ if (w.secrets !== void 0) targets.push("findings the content inspection attached to the call");
910
+ if (w.agent !== void 0) targets.push("the agent making the call");
911
+ const target = targets.length === 0 ? "The call as a whole" : targets.join("; ");
912
+ const condition = [];
913
+ if (w.agent !== void 0) condition.push(`the agent is ${quoted(list(w.agent))}`);
914
+ if (w.path?.inside !== void 0) condition.push(`the path is under ${quoted(w.path.inside)}`);
915
+ if (w.path?.outside !== void 0) condition.push(`the path is outside all of ${quoted(w.path.outside)}`);
916
+ if (w.host?.in !== void 0) condition.push(`the host is one of ${quoted(w.host.in)}`);
917
+ if (w.host?.notIn !== void 0) condition.push(`the host is not one of ${quoted(w.host.notIn)}`);
918
+ for (const a of w.arg ?? []) {
919
+ if (a.present !== void 0) condition.push(`\`${a.name}\` is ${a.present ? "present" : "absent"}`);
920
+ if (a.above !== void 0) condition.push(`\`${a.name}\` is above ${a.above}`);
921
+ if (a.below !== void 0) condition.push(`\`${a.name}\` is below ${a.below}`);
922
+ if (a.equals !== void 0) condition.push(`\`${a.name}\` equals \`${String(a.equals)}\``);
923
+ if (a.oneOf !== void 0) condition.push(`\`${a.name}\` is one of ${quoted(a.oneOf)}`);
924
+ }
925
+ if (w.count !== void 0) condition.push(`more than ${w.count.above} such calls have already run in this session`);
926
+ if (w.secrets !== void 0) {
927
+ const n = w.secrets.atLeast ?? 1;
928
+ condition.push(`${n === 1 ? "a finding" : `at least ${n} findings`}${w.secrets.types === void 0 ? "" : ` of type ${quoted(w.secrets.types)}`}${w.secrets.minConfidence === void 0 ? "" : ` with confidence ≥ ${w.secrets.minConfidence}`}`);
929
+ }
930
+ const c = w.command;
931
+ if (c !== void 0) {
932
+ if (c.name !== void 0) condition.push(`the program (or a wrapper it runs through) is ${quoted(list(c.name))}`);
933
+ const must = list(c.token).filter((t) => !t.startsWith("!"));
934
+ const never = list(c.token).filter((t) => t.startsWith("!")).map((t) => t.slice(1));
935
+ if (must.length > 0) condition.push(`it carries all of ${quoted(must)}`);
936
+ if (never.length > 0) condition.push(`it carries none of ${quoted(never)}`);
937
+ if (c.anyToken !== void 0) condition.push(`it carries any of ${quoted(list(c.anyToken))}`);
938
+ if (c.pipedTo !== void 0) condition.push(`it pipes into ${quoted(list(c.pipedTo))}`);
939
+ if (c.pipeline !== void 0) condition.push(`its pipeline has the shape ${quoted(list(c.pipeline))}`);
940
+ }
941
+ if (condition.length === 0) condition.push("always: no further condition");
942
+ const e = rule.effect;
943
+ return {
944
+ evaluates,
945
+ target,
946
+ condition,
947
+ action: e.kind === "deny" ? "Refuses the call before it runs." : e.kind === "ask" ? "Holds the call until a person allows or refuses it." : e.kind === "warn" ? "Lets the call run and records a warning." : e.kind === "allow" ? "Allows the call and names this rule in the evidence; an allow never overrides another rule's refusal." : `Rewrites ${quoted(Object.keys(e.set))} before the call runs.`
948
+ };
949
+ }
950
+ const isEvent = (e) => e.tool.startsWith("prae:") || e.tool === "system-prompt";
951
+ /**
952
+ * Per pack name, every row in which a gate showed it in force — its
953
+ * activation or reload row, or a decision attributed to it — and its
954
+ * removal rows, in ledger order.
955
+ */
956
+ function packActivity(entries) {
957
+ const out = /* @__PURE__ */ new Map();
958
+ const add = (name, seen) => {
959
+ const list$1 = out.get(name);
960
+ if (list$1 === void 0) out.set(name, [seen]);
961
+ else list$1.push(seen);
962
+ };
963
+ for (const e of entries) if (e.tool === "prae:pack") {
964
+ if (e.rule === "pack.activated" || e.rule === "pack.reloaded") add(e.pack.name, {
965
+ row: e,
966
+ kind: "in-force"
967
+ });
968
+ else if (e.rule === "pack.removed") add(e.pack.name, {
969
+ row: e,
970
+ kind: "removed"
971
+ });
972
+ } else if (!isEvent(e) && e.pack !== void 0) add(e.pack.name, {
973
+ row: e,
974
+ kind: "in-force"
975
+ });
976
+ return out;
977
+ }
978
+ /** Whether a gate confirmed this exact definition of a pack in force. */
979
+ function activityOf(pack, activity, failed) {
980
+ if (failed !== void 0) return {
981
+ state: "failed",
982
+ note: `A gate refused this definition: ${failed.why}`,
983
+ seq: failed.seq,
984
+ at: failed.at,
985
+ sessions: []
986
+ };
987
+ const rows = activity.get(pack.name) ?? [];
988
+ const same = (r) => r.pack.version === pack.version && (pack.sha256 === void 0 || r.pack.sha256 === void 0 || r.pack.sha256 === pack.sha256);
989
+ const last = rows.at(-1);
990
+ if (last?.kind === "removed") return {
991
+ state: "inactive",
992
+ note: `A gate sealed this pack removed (row #${last.row.seq}); its rules no longer apply there.`,
993
+ seq: last.row.seq,
994
+ at: last.row.at,
995
+ sessions: []
996
+ };
997
+ const confirming = rows.filter((s) => s.kind === "in-force" && same(s.row));
998
+ const newest = confirming.at(-1);
999
+ if (newest !== void 0) return {
1000
+ state: "active",
1001
+ note: `A gate showed ${pack.name} ${pack.version} in force at row #${newest.row.seq}. That is the last confirmation; PRAE has no heartbeat that says a gate is running now.`,
1002
+ seq: newest.row.seq,
1003
+ at: newest.row.at,
1004
+ sessions: [...new Set(confirming.flatMap((s) => s.row.session === void 0 ? [] : [s.row.session]))]
1005
+ };
1006
+ const other = rows.at(-1);
1007
+ return {
1008
+ state: "unconfirmed",
1009
+ note: other !== void 0 ? `Verification required: the gate last showed ${pack.name} ${other.row.pack.version} in force (row #${other.row.seq}); this configured definition has not been confirmed by any gate.` : "Verification required: configured, but no gate has sealed this pack in force in this record yet.",
1010
+ sessions: []
1011
+ };
1012
+ }
1013
+ /**
1014
+ * The rows that can be evidence: every row except the console's own
1015
+ * deployment rows. Placing a pack is an operation, not something the agent
1016
+ * did or a gate decided, so it never counts toward a control — not as a
1017
+ * recorded event, and never as a refusal.
1018
+ */
1019
+ function evidenceRows(entries) {
1020
+ return entries.filter((e) => e.tool !== "prae:deploy");
1021
+ }
1022
+ /** How long a written pack waits for a gate before it needs verification. */
1023
+ const CONFIRM_WINDOW_MS = 2e4;
1024
+ /**
1025
+ * Each deployment's lifecycle, read from the ledger alone: the console's
1026
+ * `deploy.requested` row, then what a gate sealed after it. ACTIVE only on
1027
+ * a gate's activation or reload row carrying the deployed file's exact
1028
+ * sha256; FAILED on the console's own failure row or a gate refusing the
1029
+ * file; INACTIVE when a gate later sealed it removed. With no gate row,
1030
+ * APPLYING for a short window and then VERIFICATION_REQUIRED — the file is
1031
+ * configured, and nothing has shown it enforced.
1032
+ */
1033
+ function deploymentsOf(entries, now = Date.now()) {
1034
+ const out = [];
1035
+ const requests = entries.filter((e) => e.tool === "prae:deploy" && e.deploy?.action === "requested");
1036
+ for (const r of requests) {
1037
+ const d = r.deploy;
1038
+ if (d === void 0 || r.pack.sha256 === void 0) continue;
1039
+ const sha = r.pack.sha256;
1040
+ const after = entries.filter((e) => e.seq > r.seq);
1041
+ const base = {
1042
+ operation: d.operation,
1043
+ pack: r.pack.name,
1044
+ version: r.pack.version,
1045
+ sha256: sha,
1046
+ file: d.file,
1047
+ target: d.target,
1048
+ environment: d.environment,
1049
+ rules: d.rules,
1050
+ controls: d.controls,
1051
+ ...d.operator === void 0 ? {} : { operator: d.operator },
1052
+ requestedAt: r.at,
1053
+ seq: r.seq
1054
+ };
1055
+ const failedRow = after.find((e) => e.tool === "prae:deploy" && e.deploy?.action === "failed" && e.deploy.operation === d.operation);
1056
+ if (failedRow !== void 0) {
1057
+ out.push({
1058
+ ...base,
1059
+ state: "FAILED",
1060
+ failure: "operation",
1061
+ note: `Not deployed: ${failedRow.deploy?.error ?? "the operation failed"}. Nothing was placed where a gate reads, so coverage is unchanged.`,
1062
+ runtime: {
1063
+ seq: failedRow.seq,
1064
+ at: failedRow.at
1065
+ },
1066
+ sessions: []
1067
+ });
1068
+ continue;
1069
+ }
1070
+ const confirms = after.filter((e) => e.tool === "prae:pack" && (e.rule === "pack.activated" || e.rule === "pack.reloaded") && e.pack.sha256 === sha);
1071
+ const refused = after.find((e) => e.tool === "prae:pack" && e.rule === "pack.rejected" && e.subject === d.file);
1072
+ const removed = after.find((e) => e.tool === "prae:pack" && e.rule === "pack.removed" && e.pack.sha256 === sha);
1073
+ const sessions = [...new Set(confirms.flatMap((e) => e.session === void 0 ? [] : [e.session]))];
1074
+ const at = (e) => ({
1075
+ seq: e.seq,
1076
+ at: e.at,
1077
+ ...e.session === void 0 ? {} : { session: e.session }
1078
+ });
1079
+ if (refused !== void 0) {
1080
+ out.push({
1081
+ ...base,
1082
+ state: "FAILED",
1083
+ failure: "runtime",
1084
+ note: `A gate refused the pack (row #${refused.seq}): ${(refused.reason ?? "").replace(/^pack rejected, not loaded: /, "")}. The gate kept the policy it had; this pack does not count toward coverage.`,
1085
+ runtime: at(refused),
1086
+ sessions
1087
+ });
1088
+ continue;
1089
+ }
1090
+ if (removed !== void 0) {
1091
+ out.push({
1092
+ ...base,
1093
+ state: "INACTIVE",
1094
+ note: `A gate sealed the pack removed (row #${removed.seq}); its rules no longer apply there.`,
1095
+ runtime: at(removed),
1096
+ sessions
1097
+ });
1098
+ continue;
1099
+ }
1100
+ const first = confirms[0];
1101
+ if (first !== void 0) {
1102
+ out.push({
1103
+ ...base,
1104
+ state: "ACTIVE",
1105
+ note: `A gate sealed this exact pack (sha256 ${sha.slice(0, 12)}) in force at row #${first.seq}${sessions.length > 0 ? ` · ${sessions.length} session${sessions.length === 1 ? "" : "s"}` : ""}.`,
1106
+ runtime: at(first),
1107
+ sessions
1108
+ });
1109
+ continue;
1110
+ }
1111
+ const waiting = now - Date.parse(r.at) < CONFIRM_WINDOW_MS;
1112
+ out.push({
1113
+ ...base,
1114
+ state: waiting ? "APPLYING" : "VERIFICATION_REQUIRED",
1115
+ note: waiting ? "Written to the workspace pack directory; waiting for a gate to seal it in force." : "Written and configured, but no gate has sealed it in force: no gate watching this workspace is running. A gate mounted with this workspace confirms it when it starts.",
1116
+ sessions
1117
+ });
1118
+ }
1119
+ return out;
1120
+ }
1121
+ function describeRule(rule) {
1122
+ const logic = logicOf(rule);
1123
+ return {
1124
+ id: rule.id,
1125
+ ...rule.title === void 0 ? {} : { title: rule.title },
1126
+ ...rule.description === void 0 ? {} : { description: rule.description },
1127
+ effect: rule.effect.kind,
1128
+ because: rule.effect.because,
1129
+ engines: enginesOf(rule),
1130
+ ...logic === void 0 ? {} : { logic },
1131
+ definition: JSON.stringify({
1132
+ when: rule.when,
1133
+ effect: rule.effect
1134
+ }, null, 2),
1135
+ cites: Object.entries(rule.frameworks ?? {}).flatMap(([f, cs]) => cs.map((c) => `${f}:${c}`))
1136
+ };
1137
+ }
1138
+ function ruleViews(pack, origins, entries, admitted, environment, failedPacks = /* @__PURE__ */ new Map()) {
1139
+ const activity = packActivity(entries);
1140
+ const byRule = /* @__PURE__ */ new Map();
1141
+ for (const e of admitted) {
1142
+ if (e.rule === void 0 || isEvent(e)) continue;
1143
+ const rows = byRule.get(e.rule);
1144
+ if (rows === void 0) byRule.set(e.rule, [e]);
1145
+ else rows.push(e);
1146
+ }
1147
+ const out = {};
1148
+ for (const rule of pack?.rules ?? []) {
1149
+ const ref = origins.get(rule.id) ?? {
1150
+ name: pack?.pack ?? "unknown",
1151
+ version: pack?.version ?? ""
1152
+ };
1153
+ const rows = byRule.get(rule.id) ?? [];
1154
+ const last = rows.at(-1);
1155
+ out[rule.id] = {
1156
+ ...describeRule(rule),
1157
+ pack: ref,
1158
+ scope: {
1159
+ agents: rule.when.agent === void 0 ? "all" : list(rule.when.agent),
1160
+ environment
1161
+ },
1162
+ active: activityOf(ref, activity, ref.sha256 === void 0 ? void 0 : failedPacks.get(ref.sha256)),
1163
+ evidence: {
1164
+ count: rows.length,
1165
+ ...last === void 0 ? {} : { last: {
1166
+ seq: last.seq,
1167
+ at: last.at
1168
+ } }
1169
+ }
1170
+ };
1171
+ }
1172
+ return out;
1173
+ }
1174
+ /**
1175
+ * Deployed packs a gate refused, by sha256 — the latest deployment of each
1176
+ * definition decides — kept out of coverage: the gate does not enforce them.
1177
+ */
1178
+ function refusedDeployments(deployments) {
1179
+ const latest = /* @__PURE__ */ new Map();
1180
+ for (const d of deployments) latest.set(d.sha256, d);
1181
+ return new Map([...latest.values()].filter((d) => d.failure === "runtime").map((d) => [d.sha256, {
1182
+ seq: d.runtime?.seq ?? d.seq,
1183
+ at: d.runtime?.at ?? d.requestedAt,
1184
+ why: d.note
1185
+ }]));
1186
+ }
1187
+
869
1188
  //#endregion
870
1189
  //#region ../controls/src/scope.ts
871
1190
  const ROW_LIMIT = 50;
@@ -943,10 +1262,11 @@ function catalogOf(file) {
943
1262
  };
944
1263
  frameworks.set(framework.id, f);
945
1264
  }
1265
+ const name = control.name ?? FRAMEWORKS[framework.id]?.controls[id]?.name;
946
1266
  f.controls.push({
947
1267
  key,
948
1268
  id,
949
- ...control.name === void 0 ? {} : { name: control.name },
1269
+ ...name === void 0 ? {} : { name },
950
1270
  asks: control.asks
951
1271
  });
952
1272
  };
@@ -1040,6 +1360,11 @@ function inPeriod(at, period, now) {
1040
1360
  if (period.kind === "days") return t >= now - period.days * 864e5;
1041
1361
  return t >= Date.parse(period.from) && t < Date.parse(period.to) + 864e5;
1042
1362
  }
1363
+ /** The rows a scope admits: its agents, its sessions, its period. */
1364
+ function admit(entries, scope, agent, now = Date.now()) {
1365
+ const agentOf$1 = (e) => e.agent ?? agent;
1366
+ return entries.filter((e) => (scope.agents === "all" || scope.agents.includes(agentOf$1(e))) && (scope.sessions === "all" || e.session !== void 0 && scope.sessions.includes(e.session)) && inPeriod(e.at, scope.period, now));
1367
+ }
1043
1368
  /**
1044
1369
  * Assess the scoped controls against the record. The rows are narrowed to
1045
1370
  * the scope's agents, sessions and period before any control reads them;
@@ -1049,7 +1374,7 @@ function inPeriod(at, period, now) {
1049
1374
  function assess(data, scope, catalog, options) {
1050
1375
  const now = options.now ?? Date.now();
1051
1376
  const agentOf$1 = (e) => e.agent ?? options.agent;
1052
- const admitted = data.entries.filter((e) => (scope.agents === "all" || scope.agents.includes(agentOf$1(e))) && (scope.sessions === "all" || e.session !== void 0 && scope.sessions.includes(e.session)) && inPeriod(e.at, scope.period, now));
1377
+ const admitted = admit(data.entries, scope, options.agent, now);
1053
1378
  const scoped = {
1054
1379
  ...data,
1055
1380
  entries: admitted
@@ -1196,17 +1521,39 @@ function agentOf(dir) {
1196
1521
  } catch {}
1197
1522
  return "this agent";
1198
1523
  }
1199
- function assessCompliance(record, data) {
1524
+ /** What a record's compliance is assessed against: its catalog and scope. */
1525
+ function complianceContext(record) {
1200
1526
  const { catalog, catalogError } = catalogFor(record.dir);
1201
1527
  return {
1202
- ...assess(data, scopeFor(record.dir, catalog), catalog, {
1528
+ catalog,
1529
+ ...catalogError === void 0 ? {} : { catalogError },
1530
+ scope: scopeFor(record.dir, catalog),
1531
+ options: {
1203
1532
  agent: agentOf(record.dir),
1204
1533
  environment: basename(resolve(record.dir))
1205
- }),
1534
+ }
1535
+ };
1536
+ }
1537
+ function assessCompliance(record, data) {
1538
+ const { catalog, catalogError, scope, options } = complianceContext(record);
1539
+ return {
1540
+ ...assess(data, scope, catalog, options),
1206
1541
  saved: existsSync(resolve(record.dir, SCOPE_FILE)),
1207
1542
  ...catalogError === void 0 ? {} : { catalogError }
1208
1543
  };
1209
1544
  }
1545
+ /**
1546
+ * The Rule Inspector's rules (PRA-195): every rule in force, with where it
1547
+ * came from, how it is evaluated, whether a gate confirmed its pack in
1548
+ * force, and its sealed decisions within the scope. Read-only.
1549
+ */
1550
+ function inspectorOf(record, data, scope, origins, deployments, refused) {
1551
+ const agent = agentOf(record.dir);
1552
+ return {
1553
+ rules: ruleViews(data.pack, origins, data.entries, admit(data.entries, scope, agent), basename(resolve(record.dir)), refused),
1554
+ deployments
1555
+ };
1556
+ }
1210
1557
 
1211
1558
  //#endregion
1212
1559
  //#region src/serve.ts
@@ -1395,15 +1742,30 @@ function complianceOf(record) {
1395
1742
  problems: grown.problems
1396
1743
  };
1397
1744
  const { set } = packSetOf(record);
1398
- const pack = set.packs.length > 0 ? set.effective : void 0;
1745
+ const deployments = deploymentsOf(entries);
1746
+ const refused = refusedDeployments(deployments);
1747
+ const kept = set.packs.filter((p) => !refused.has(p.sha256));
1748
+ const pack = kept.length === 0 ? void 0 : kept.length === set.packs.length ? set.effective : {
1749
+ ...set.effective,
1750
+ rules: kept.flatMap((p) => p.pack.rules)
1751
+ };
1399
1752
  const data = {
1400
1753
  ...pack === void 0 ? {} : { pack },
1401
- entries,
1754
+ entries: evidenceRows(entries),
1402
1755
  verification,
1403
1756
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1404
1757
  };
1758
+ const assessment = assessCompliance(record, data);
1759
+ const origins = new Map(set.packs.flatMap((p) => p.pack.rules.map((r) => [r.id, {
1760
+ name: p.pack.pack,
1761
+ version: p.pack.version,
1762
+ sha256: p.sha256,
1763
+ origin: p.origin,
1764
+ file: p.file.split("/").at(-1) ?? p.file
1765
+ }])));
1405
1766
  return {
1406
- assessment: assessCompliance(record, data),
1767
+ assessment,
1768
+ inspector: inspectorOf(record, data, assessment.scope, origins, deployments, refused),
1407
1769
  controls: CONTROLS.map((control) => {
1408
1770
  const found = Object.entries(FRAMEWORKS).find(([, f]) => control.id.startsWith(`${f.short} · `));
1409
1771
  const code = found === void 0 ? void 0 : control.id.split(" · ")[1];
@@ -1822,7 +2184,7 @@ async function check(path, argv = []) {
1822
2184
  * from the findings, so there is nothing here to be careful about printing.
1823
2185
  */
1824
2186
  async function scanFile(path, packPath, json) {
1825
- const { scan } = await import("./src-DMoYsdfo.js");
2187
+ const { scan } = await import("./src-CwBEFhww.js");
1826
2188
  let content;
1827
2189
  try {
1828
2190
  content = await readFile(path, "utf8");
@@ -1862,13 +2224,13 @@ async function scanFile(path, packPath, json) {
1862
2224
  * not come up inspecting nothing.
1863
2225
  */
1864
2226
  async function mcp(root, packPath, ledgerPath) {
1865
- const { serveMcp } = await import("./mcp-D521EoP6.js");
2227
+ const { serveMcp } = await import("./mcp-DdEDvTvR.js");
1866
2228
  const { resolve: resolve$1 } = await import("node:path");
1867
2229
  const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
1868
2230
  const chosen = packPath ?? bundled;
1869
2231
  if (chosen === void 0) return fail("mcp needs --pack <file>; no bundled secret-catcher found");
1870
2232
  const pack = await loadPack(chosen);
1871
- const { Ledger: Ledger$1 } = await import("./src-wG36kT6K.js");
2233
+ const { Ledger: Ledger$1 } = await import("./src-2iJy5MZc.js");
1872
2234
  const ledger = ledgerPath === void 0 ? void 0 : Ledger$1.open(ledgerPath);
1873
2235
  console.error(`prae mcp: serving ${resolve$1(root)} under ${pack.pack} ${pack.version}` + (ledgerPath === void 0 ? " (no ledger)" : ` · ledger ${ledgerPath}`));
1874
2236
  await serveMcp({
@@ -1920,7 +2282,7 @@ async function explain(path, tool, pairs) {
1920
2282
  * a key that arrived beside it is checked against nothing.
1921
2283
  */
1922
2284
  async function keygen(dir) {
1923
- const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2285
+ const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
1924
2286
  const { writeFileSync: writeFileSync$1 } = await import("node:fs");
1925
2287
  const keys = generateKeypair();
1926
2288
  const priv = join(dir, "prae-signing.pem");
@@ -1943,7 +2305,7 @@ async function keygen(dir) {
1943
2305
  async function keygenSealed(role, dir) {
1944
2306
  if (role !== "operator" && role !== "policy" && role !== "ledger") fail("--role is operator, policy or ledger");
1945
2307
  const stem = role === "ledger" ? "prae-signing" : role;
1946
- const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2308
+ const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
1947
2309
  const { writeFileSync: writeFileSync$1 } = await import("node:fs");
1948
2310
  const keys = sealedKeypair(process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: "));
1949
2311
  const pub = join(dir, `${stem}.pub.pem`);
@@ -1956,7 +2318,7 @@ async function keygenSealed(role, dir) {
1956
2318
  }
1957
2319
  /** A detached signature over a pack's bytes (PRA-107). */
1958
2320
  async function signPack(packPath, keyPath) {
1959
- const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2321
+ const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
1960
2322
  const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
1961
2323
  const bytes = readFileSync(packPath);
1962
2324
  const parsed$1 = parsePack(bytes.toString("utf8"));
@@ -2001,7 +2363,7 @@ function askHidden(prompt) {
2001
2363
  });
2002
2364
  }
2003
2365
  async function receipt(path, seq, keyPath, out) {
2004
- const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2366
+ const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
2005
2367
  const { readFileSync: readFileSync$1 } = await import("node:fs");
2006
2368
  const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
2007
2369
  const privateKey = readFileSync$1(keyPath, "utf8");
@@ -2031,7 +2393,7 @@ async function receipt(path, seq, keyPath, out) {
2031
2393
  * file and sets the exit code, so scripts can branch on it.
2032
2394
  */
2033
2395
  async function verifyReceiptCmd(path) {
2034
- const { checkReceipt } = await import("./receipt-check-CUuEjGHL.js");
2396
+ const { checkReceipt } = await import("./receipt-check-2F4zLEW5.js");
2035
2397
  let parsed$1;
2036
2398
  try {
2037
2399
  parsed$1 = JSON.parse(await readFile(path, "utf8"));
@@ -2062,7 +2424,7 @@ async function test(suitePath, packOverride, asJson) {
2062
2424
  } catch (error) {
2063
2425
  return fail(`${suitePath} is not valid JSON: ${error instanceof Error ? error.message : error}`);
2064
2426
  }
2065
- const { parseSuite, renderSuite, runSuite } = await import("./test-run-C8dHjIE2.js");
2427
+ const { parseSuite, renderSuite, runSuite } = await import("./test-run-BGsK-inZ.js");
2066
2428
  const suite = parseSuite(parsed$1);
2067
2429
  if (!suite.ok) {
2068
2430
  console.error(`prae: ${suitePath} is not a valid test suite`);
@@ -2073,7 +2435,7 @@ async function test(suitePath, packOverride, asJson) {
2073
2435
  if (chosen === void 0) return fail("test needs a pack: set `pack` in the suite or pass --pack");
2074
2436
  const pack = await loadPack(chosen);
2075
2437
  const { capabilityForTool } = await import("capmark");
2076
- const { scan } = await import("./src-DMoYsdfo.js");
2438
+ const { scan } = await import("./src-CwBEFhww.js");
2077
2439
  const inspect = pack.rules.some((rule) => rule.when.secrets !== void 0) ? (path) => {
2078
2440
  try {
2079
2441
  return scan(readFileSync(path, "utf8"));
@@ -2102,14 +2464,14 @@ async function verify$1(path, keyPath, checkpointPath) {
2102
2464
  const last = entries.at(-1);
2103
2465
  console.log(`${result.entries} entries · chain intact${last === void 0 ? "" : ` · seal ${seal(last)}`}`);
2104
2466
  if (checkpointPath !== void 0) {
2105
- const { checkCheckpoint } = await import("./checkpoint-BqJ8xwky.js");
2467
+ const { checkCheckpoint } = await import("./checkpoint-BLOGdWFu.js");
2106
2468
  const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
2107
2469
  const verdict = checkCheckpoint(entries, JSON.parse(readFileSync(checkpointPath, "utf8")), publicKey);
2108
2470
  for (const line of verdict.lines) (verdict.ok ? console.log : console.error)(`checkpoint: ${line}`);
2109
2471
  if (!verdict.ok) process.exitCode = 1;
2110
2472
  }
2111
2473
  if (keyPath !== void 0) {
2112
- const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2474
+ const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
2113
2475
  const { readFileSync: readFileSync$1 } = await import("node:fs");
2114
2476
  const publicKey = readFileSync$1(keyPath, "utf8");
2115
2477
  let signed = 0;
@@ -2187,7 +2549,7 @@ async function report(path, out, packPath) {
2187
2549
  * that step is a proposal a human approves, and it starts from this list.
2188
2550
  */
2189
2551
  async function instructions(from, asJson) {
2190
- const { ingest } = await import("./src-CDYyZyeU.js");
2552
+ const { ingest } = await import("./src-XukJfuml.js");
2191
2553
  const result = ingest(from);
2192
2554
  if (asJson) {
2193
2555
  console.log(JSON.stringify(result, null, 2));
@@ -2223,7 +2585,7 @@ async function instructions(from, asJson) {
2223
2585
  * on purpose rather than something that happened to their policy.
2224
2586
  */
2225
2587
  async function propose(from, options) {
2226
- const [{ ingest }, { extract }] = await Promise.all([import("./src-CDYyZyeU.js"), import("./src-BlmsTGdY.js")]);
2588
+ const [{ ingest }, { extract }] = await Promise.all([import("./src-XukJfuml.js"), import("./src-BoBCmiwj.js")]);
2227
2589
  const read = ingest(from);
2228
2590
  if (read.files.length === 0) {
2229
2591
  console.log(`No instruction files found at or above ${from}.`);
@@ -2283,9 +2645,9 @@ async function consoleOut(path, argv) {
2283
2645
  const instructionsDir = flag(argv, "instructions");
2284
2646
  if (instructionsDir !== void 0) {
2285
2647
  const [{ ingest }, { extract }, { renderInstructions }] = await Promise.all([
2286
- import("./src-CDYyZyeU.js"),
2287
- import("./src-BlmsTGdY.js"),
2288
- import("./instructions-DihPiiC2.js")
2648
+ import("./src-XukJfuml.js"),
2649
+ import("./src-BoBCmiwj.js"),
2650
+ import("./instructions-fCJ4j7ce.js")
2289
2651
  ]);
2290
2652
  const read = ingest(instructionsDir);
2291
2653
  instructionsView = renderInstructions(data, read, extract(read.statements));
@@ -2333,11 +2695,11 @@ function bundledBaseline() {
2333
2695
  async function init(argv) {
2334
2696
  const packPath = flag(argv, "pack") ?? bundledBaseline();
2335
2697
  if (packPath === void 0) fail("init needs --pack <file>; no bundled baseline found");
2698
+ const pack = await loadPack(packPath);
2336
2699
  if (flag(argv, "pack") === void 0) {
2337
- console.log("No --pack given; mounting the built-in baseline (8 rules).");
2700
+ console.log(`No --pack given; mounting the built-in baseline (${pack.rules.length} rules).`);
2338
2701
  console.log(`See it with: prae check ${packPath}\n`);
2339
2702
  }
2340
- const pack = await loadPack(packPath);
2341
2703
  const source = await readFile(packPath, "utf8");
2342
2704
  const link = argv.includes("--link");
2343
2705
  const builtin = link ? { packFile: resolve(packPath) } : { pack: source };
@@ -2418,7 +2780,7 @@ async function chainMatrix(path, argv) {
2418
2780
  const catalogue = guardiansPath === void 0 ? [] : JSON.parse(readFileSync(guardiansPath, "utf8")).guardians;
2419
2781
  const guardianOf = (rule) => rule === void 0 ? void 0 : catalogue.find((g) => g.rules.includes(rule) || (g.armed ?? []).includes(rule))?.name;
2420
2782
  const keyPath = flag(argv, "key");
2421
- const { verifySignature: verifySignature$1 } = await import("./src-wG36kT6K.js");
2783
+ const { verifySignature: verifySignature$1 } = await import("./src-2iJy5MZc.js");
2422
2784
  const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
2423
2785
  const matrix = integrityMatrix(entries, pack, guardianOf).map((m) => {
2424
2786
  const entry = entries.find((e) => e.seq === m.seq);
@@ -2449,7 +2811,7 @@ async function chainMatrix(path, argv) {
2449
2811
  if (!chainOk || sigFailed) process.exitCode = 1;
2450
2812
  }
2451
2813
  async function checkpoint(path, keyPath, out) {
2452
- const { makeCheckpoint } = await import("./checkpoint-BqJ8xwky.js");
2814
+ const { makeCheckpoint } = await import("./checkpoint-BLOGdWFu.js");
2453
2815
  let ledger;
2454
2816
  try {
2455
2817
  ledger = Ledger.open(path);
@@ -2470,7 +2832,7 @@ async function checkpoint(path, keyPath, out) {
2470
2832
  }
2471
2833
  }
2472
2834
  async function exportLedger(path, options) {
2473
- const { toOcsf, ndjson, sendToHec } = await import("./ocsf-DF9cMiQJ.js");
2835
+ const { toOcsf, ndjson, sendToHec } = await import("./ocsf-BwWyM7hc.js");
2474
2836
  const { createRequire: createRequire$1 } = await import("node:module");
2475
2837
  const { version } = createRequire$1(import.meta.url)("../package.json");
2476
2838
  let ledger;
@@ -2601,7 +2963,7 @@ async function main(argv) {
2601
2963
  }
2602
2964
  case "init":
2603
2965
  if (rest[0] === "claude-code") {
2604
- const { initClaudeCode, stableNode } = await import("./init-DRd-IRR6.js");
2966
+ const { initClaudeCode, stableNode } = await import("./init-BPlBJvA-.js");
2605
2967
  try {
2606
2968
  return initClaudeCode(rest.slice(1), {
2607
2969
  cwd: process.cwd(),
@@ -2616,7 +2978,7 @@ async function main(argv) {
2616
2978
  return init(rest);
2617
2979
  case "hook": {
2618
2980
  if (rest[0] !== "claude-code") fail("hook takes claude-code");
2619
- const { runHook } = await import("./hook-Gll2spTP.js");
2981
+ const { runHook } = await import("./hook-DxLgNtc4.js");
2620
2982
  return runHook(rest.slice(1));
2621
2983
  }
2622
2984
  case "--help":