prae-cli 0.9.1 → 0.9.2

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.
@@ -348,6 +348,7 @@ function pathArgOf(args, explicit) {
348
348
  * appending to a pack can never accidentally widen what an agent may do.
349
349
  */
350
350
  const STRENGTH = {
351
+ allow: 0,
351
352
  warn: 1,
352
353
  correct: 2,
353
354
  ask: 3,
@@ -479,6 +480,14 @@ function evaluate(pack, facts) {
479
480
  evaluated
480
481
  };
481
482
  }
483
+ const allowed = hits.find((hit) => hit.effect.kind === "allow");
484
+ if (allowed !== void 0) return {
485
+ kind: "allow",
486
+ rule: allowed.rule.id,
487
+ reason: allowed.effect.because,
488
+ trace,
489
+ evaluated
490
+ };
482
491
  return {
483
492
  kind: "allow",
484
493
  trace,
@@ -487,4 +496,4 @@ function evaluate(pack, facts) {
487
496
  }
488
497
 
489
498
  //#endregion
490
- export { evaluate as t };
499
+ export { hostOf as n, withinAny as r, evaluate as t };
package/lib/index.d.ts CHANGED
@@ -36,6 +36,8 @@ declare function praeRow(options: {
36
36
  readonly pack?: string;
37
37
  /** The pack as a file the gate watches (hot reload); wins over `pack`. */
38
38
  readonly packFile?: string;
39
+ /** The workspace's own packs, loaded and watched beside `pack` / `packFile`. */
40
+ readonly packDir?: string;
39
41
  /** `shadow` seals every verdict and lets every call through. */
40
42
  readonly mode?: 'enforce' | 'shadow';
41
43
  readonly ledger?: string;
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import "./evaluate-wMPkjcIC.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";
1
+ import "./evaluate-BqLxhYiP.js";
2
+ import "./views-D4UEfbzt.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-B_B6-bUZ.js";
4
+ import "./src-LA7NdLKb.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-CIg2ZNG0.js";
1
+ import { c as renderNav, l as renderShell, s as esc, t as navFor } from "./views-D4UEfbzt.js";
2
2
 
3
3
  //#region src/console/instructions.ts
4
4
  const STYLE = `
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "./evaluate-wMPkjcIC.js";
1
+ import { t as evaluate } from "./evaluate-BqLxhYiP.js";
2
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";
package/lib/playground.js CHANGED
@@ -351,6 +351,7 @@
351
351
  * appending to a pack can never accidentally widen what an agent may do.
352
352
  */
353
353
  const STRENGTH = {
354
+ allow: 0,
354
355
  warn: 1,
355
356
  correct: 2,
356
357
  ask: 3,
@@ -482,6 +483,14 @@
482
483
  evaluated
483
484
  };
484
485
  }
486
+ const allowed = hits.find((hit) => hit.effect.kind === "allow");
487
+ if (allowed !== void 0) return {
488
+ kind: "allow",
489
+ rule: allowed.rule.id,
490
+ reason: allowed.effect.because,
491
+ trace,
492
+ evaluated
493
+ };
485
494
  return {
486
495
  kind: "allow",
487
496
  trace,
@@ -854,9 +863,9 @@
854
863
  check.fail(path, "expected an object");
855
864
  return;
856
865
  }
857
- const kind = value.kind;
858
- if (kind !== "deny" && kind !== "ask" && kind !== "correct" && kind !== "warn") {
859
- check.fail(`${path}.kind`, "expected `deny`, `ask`, `correct`, or `warn`");
866
+ const kind = value.kind === "hold" ? "ask" : value.kind;
867
+ if (kind !== "deny" && kind !== "ask" && kind !== "correct" && kind !== "warn" && kind !== "allow") {
868
+ check.fail(`${path}.kind`, "expected `allow`, `deny`, `hold` (or `ask`), `correct`, or `warn`");
860
869
  return;
861
870
  }
862
871
  check.keys(path, value, kind === "correct" ? [
@@ -890,6 +899,7 @@
890
899
  check.keys(path, value, [
891
900
  "id",
892
901
  "title",
902
+ "description",
893
903
  "when",
894
904
  "effect",
895
905
  "frameworks"
@@ -908,6 +918,10 @@
908
918
  const title = check.text(`${path}.title`, value.title);
909
919
  if (title !== void 0) rule.title = title;
910
920
  }
921
+ if (value.description !== void 0) {
922
+ const description = check.text(`${path}.description`, value.description);
923
+ if (description !== void 0) rule.description = description;
924
+ }
911
925
  if (value.frameworks !== void 0) {
912
926
  const frameworks = readFrameworks(check, `${path}.frameworks`, value.frameworks);
913
927
  if (frameworks !== void 0) rule.frameworks = frameworks;
@@ -968,12 +982,16 @@
968
982
  "prae",
969
983
  "pack",
970
984
  "version",
985
+ "description",
986
+ "owner",
971
987
  "baseline",
972
988
  "rules"
973
989
  ]);
974
990
  if (raw.prae !== "0.1") check.fail(".prae", "expected `\"0.1\"`; this is the only pack version this build reads");
975
991
  const name = check.text(".pack", raw.pack);
976
992
  const version = check.text(".version", raw.version);
993
+ const description = raw.description === void 0 ? void 0 : check.text(".description", raw.description);
994
+ const owner = raw.owner === void 0 ? void 0 : check.text(".owner", raw.owner);
977
995
  let baseline;
978
996
  if (raw.baseline !== void 0) if (!isObject(raw.baseline)) check.fail(".baseline", "expected an object");
979
997
  else {
@@ -1010,6 +1028,8 @@
1010
1028
  prae: "0.1",
1011
1029
  pack: name,
1012
1030
  version,
1031
+ ...description === void 0 ? {} : { description },
1032
+ ...owner === void 0 ? {} : { owner },
1013
1033
  ...baseline === void 0 ? {} : { baseline },
1014
1034
  rules
1015
1035
  }
@@ -1,4 +1,4 @@
1
- import { d as verifyReceipt, s as fingerprint } from "./src-D7g4lvaY.js";
1
+ import { c as fingerprint, f as verifyReceipt } from "./src-LA7NdLKb.js";
2
2
 
3
3
  //#region src/receipt-check.ts
4
4
  /** Shape we require before we will call it a receipt at all. */
@@ -0,0 +1,3 @@
1
+ import { _ as hashEntry, a as signBytes, c as fingerprint, d as signEntry, f as verifyReceipt, g as chain, h as canonical, i as sealedKeypair, l as generateKeypair, m as GENESIS, n as readEntries, o as unsealPrivateKey, p as verifySignature, r as verifyEntries, s as verifyBytes, t as Ledger, u as makeReceipt, v as seal } from "./src-LA7NdLKb.js";
2
+
3
+ export { GENESIS, Ledger, canonical, chain, fingerprint, generateKeypair, hashEntry, makeReceipt, readEntries, seal, sealedKeypair, signBytes, signEntry, unsealPrivateKey, verifyBytes, verifyEntries, verifyReceipt, verifySignature };
@@ -116,6 +116,13 @@ const ANSWER_TTL_MS = 5 * 6e4;
116
116
  function signBytes(bytes, privateKey) {
117
117
  return sign(null, Buffer.from(bytes), priv(privateKey)).toString("base64");
118
118
  }
119
+ function verifyBytes(bytes, signature, publicKeyPem) {
120
+ try {
121
+ return verify(null, Buffer.from(bytes), createPublicKey(publicKeyPem), Buffer.from(signature.trim(), "base64"));
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
119
126
  /**
120
127
  * How a private key is sealed at rest: scrypt (N = 2^17, r = 8, p = 1) over
121
128
  * the passphrase, AES-256-GCM over the key. The sealed file can sit where an
@@ -389,4 +396,4 @@ var Ledger = class Ledger {
389
396
  const nap = new Int32Array(new SharedArrayBuffer(4));
390
397
 
391
398
  //#endregion
392
- export { seal as _, signBytes as a, generateKeypair as c, verifyReceipt as d, verifySignature as f, hashEntry as g, chain as h, sealedKeypair as i, makeReceipt as l, canonical as m, readEntries as n, unsealPrivateKey as o, GENESIS as p, verifyEntries as r, fingerprint as s, Ledger as t, signEntry as u };
399
+ export { hashEntry as _, signBytes as a, fingerprint as c, signEntry as d, verifyReceipt as f, chain as g, canonical as h, sealedKeypair as i, generateKeypair as l, GENESIS as m, readEntries as n, unsealPrivateKey as o, verifySignature as p, verifyEntries as r, verifyBytes as s, Ledger as t, makeReceipt as u, seal as v };
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "./evaluate-wMPkjcIC.js";
1
+ import { t as evaluate } from "./evaluate-BqLxhYiP.js";
2
2
 
3
3
  //#region src/test-run.ts
4
4
  /** The path argument a scenario's call carries, if any. */
@@ -799,6 +799,18 @@ function renderNav(items, active) {
799
799
 
800
800
  //#endregion
801
801
  //#region ../controls/src/index.ts
802
+ const EU_AI_ACT = {
803
+ id: "eu-ai-act-2024",
804
+ name: "EU AI Act — Regulation (EU) 2024/1689"
805
+ };
806
+ const NIST_600_1 = {
807
+ id: "nist-ai-600-1-2024",
808
+ name: "NIST AI 600-1 (2024) — Generative AI profile"
809
+ };
810
+ const SOC_2 = {
811
+ id: "soc2-tsc-2017",
812
+ name: "SOC 2 — Trust Services Criteria (2017)"
813
+ };
802
814
  /**
803
815
  * What each control asks for, and what this profile actually produces.
804
816
  *
@@ -807,9 +819,35 @@ function renderNav(items, active) {
807
819
  * the obligation, and a tool that told them the answer would be selling the
808
820
  * one thing it cannot supply.
809
821
  */
822
+ const isEvent = (e) => e.tool.startsWith("prae:") || e.tool === "system-prompt";
823
+ const decisionsOf = (data) => data.entries.filter((e) => !isEvent(e));
824
+ const allRules = (data) => (data.pack?.rules ?? []).map((r) => r.id);
825
+ /** Rows under the given rules. */
826
+ const under = (data, rules) => {
827
+ const ids = new Set(rules);
828
+ return {
829
+ rules,
830
+ rows: data.entries.filter((e) => e.rule !== void 0 && ids.has(e.rule))
831
+ };
832
+ };
833
+ /** Refusals and holds, with the rules that produced them. */
834
+ function heldAndRefused(data) {
835
+ const rows = decisionsOf(data).filter((e) => e.verdict === "deny" || e.verdict === "ask");
836
+ return {
837
+ rules: [...new Set(rows.flatMap((e) => e.rule ? [e.rule] : []))],
838
+ rows
839
+ };
840
+ }
810
841
  const OBLIGATIONS = [
811
842
  {
812
843
  id: "EU AI Act art. 12",
844
+ code: "Art. 12",
845
+ name: "Record-keeping",
846
+ standard: EU_AI_ACT,
847
+ trace: (data) => ({
848
+ rules: [],
849
+ rows: data.entries
850
+ }),
813
851
  asks: "Automatic recording of events over the lifetime of the system, to a degree that enables tracing.",
814
852
  evidence: (data) => ({
815
853
  state: data.entries.length > 0 ? "covered" : "partial",
@@ -818,6 +856,12 @@ const OBLIGATIONS = [
818
856
  },
819
857
  {
820
858
  id: "NIST AI 600-1 · GV-1.2",
859
+ name: "Trustworthy AI in policy",
860
+ standard: NIST_600_1,
861
+ trace: (data) => ({
862
+ rules: allRules(data),
863
+ rows: []
864
+ }),
821
865
  asks: "Characteristics of trustworthy AI are integrated into organisational policies.",
822
866
  evidence: (data) => ({
823
867
  state: data.pack === void 0 ? "absent" : "covered",
@@ -826,6 +870,8 @@ const OBLIGATIONS = [
826
870
  },
827
871
  {
828
872
  id: "NIST AI 600-1 · MS-2.6",
873
+ name: "Regular safety evaluation",
874
+ standard: NIST_600_1,
829
875
  asks: "AI system is evaluated regularly for safety risks, with results documented.",
830
876
  evidence: () => ({
831
877
  state: "partial",
@@ -834,6 +880,9 @@ const OBLIGATIONS = [
834
880
  },
835
881
  {
836
882
  id: "SOC 2 · CC7.2",
883
+ name: "Monitoring for anomalies",
884
+ standard: SOC_2,
885
+ trace: (data) => heldAndRefused(data),
837
886
  asks: "The entity monitors system components for anomalies indicative of malicious acts.",
838
887
  evidence: (data) => ({
839
888
  state: data.entries.some((e) => e.verdict === "deny") ? "covered" : "partial",
@@ -842,6 +891,12 @@ const OBLIGATIONS = [
842
891
  },
843
892
  {
844
893
  id: "SOC 2 · CC6.1",
894
+ name: "Logical access restriction",
895
+ standard: SOC_2,
896
+ trace: (data) => ({
897
+ rules: allRules(data),
898
+ rows: []
899
+ }),
845
900
  asks: "Logical access to protected information assets is restricted to authorised users.",
846
901
  evidence: () => ({
847
902
  state: "partial",
@@ -937,6 +992,28 @@ function frameworkRow(frameworkId, control) {
937
992
  return {
938
993
  id: `${framework?.short ?? frameworkId} · ${control}`,
939
994
  asks: `${name}. ${risk?.summary ?? ""}`.trim(),
995
+ trace: (data) => {
996
+ const by = BOUNDED_BY[control];
997
+ if (by !== void 0) return under(data, citing(data.pack, by.framework, by.control).map((r) => r.id));
998
+ const rules = citing(data.pack, frameworkId, control).map((r) => r.id);
999
+ if (rules.length > 0) return under(data, rules);
1000
+ if (control === "ASI06") return {
1001
+ rules: [],
1002
+ rows: data.entries.filter((e) => e.rule === "PRAE-ASM" || e.rule === "PRAE-CTX")
1003
+ };
1004
+ if (control === "ASI10") return {
1005
+ rules: [
1006
+ "ASI02",
1007
+ "ASI03",
1008
+ "ASI08"
1009
+ ].flatMap((c) => citing(data.pack, AGENTIC, c)).map((r) => r.id),
1010
+ rows: decisionsOf(data).filter((e) => e.verdict === "deny")
1011
+ };
1012
+ return {
1013
+ rules: [],
1014
+ rows: []
1015
+ };
1016
+ },
940
1017
  evidence: (data) => {
941
1018
  if (data.pack === void 0) return {
942
1019
  state: "absent",
@@ -975,8 +1052,6 @@ const ISO_27001 = {
975
1052
  id: "iso-iec-27001-2022",
976
1053
  name: "ISO/IEC 27001:2022 — Annex A"
977
1054
  };
978
- const isEvent = (e) => e.tool.startsWith("prae:") || e.tool === "system-prompt";
979
- const decisionsOf = (data) => data.entries.filter((e) => !isEvent(e));
980
1055
  /** Rules in the pack whose `when` names a capability matching `test`. */
981
1056
  function rulesOn(pack, test) {
982
1057
  return (pack?.rules ?? []).filter((rule) => {
@@ -1018,6 +1093,10 @@ const ISO_CONTROLS = [
1018
1093
  id: "ISO/IEC 42001 · A.2.2",
1019
1094
  name: "AI policy",
1020
1095
  standard: ISO_42001,
1096
+ trace: (data) => ({
1097
+ rules: allRules(data),
1098
+ rows: []
1099
+ }),
1021
1100
  asks: "The organisation has a documented policy for developing or using AI systems.",
1022
1101
  evidence: (data) => data.pack === void 0 ? {
1023
1102
  state: "absent",
@@ -1031,6 +1110,7 @@ const ISO_CONTROLS = [
1031
1110
  id: "ISO/IEC 42001 · A.6.2.6",
1032
1111
  name: "AI system operation and monitoring",
1033
1112
  standard: ISO_42001,
1113
+ trace: heldAndRefused,
1034
1114
  asks: "The AI system is monitored while it operates, and what is found is acted on.",
1035
1115
  evidence: monitored
1036
1116
  },
@@ -1038,6 +1118,10 @@ const ISO_CONTROLS = [
1038
1118
  id: "ISO/IEC 42001 · A.6.2.8",
1039
1119
  name: "Recording of event logs",
1040
1120
  standard: ISO_42001,
1121
+ trace: (data) => ({
1122
+ rules: [],
1123
+ rows: data.entries
1124
+ }),
1041
1125
  asks: "Event logs are kept while the AI system is in use.",
1042
1126
  evidence: recorded
1043
1127
  },
@@ -1045,6 +1129,13 @@ const ISO_CONTROLS = [
1045
1129
  id: "ISO/IEC 42001 · A.9.4",
1046
1130
  name: "Intended use of the AI system",
1047
1131
  standard: ISO_42001,
1132
+ trace: (data) => {
1133
+ const rows = decisionsOf(data).filter((e) => e.verdict === "deny");
1134
+ return {
1135
+ rules: [...new Set(rows.flatMap((e) => e.rule ? [e.rule] : []))],
1136
+ rows
1137
+ };
1138
+ },
1048
1139
  asks: "The AI system is used only for its intended uses.",
1049
1140
  evidence: (data) => {
1050
1141
  if (data.pack === void 0) return {
@@ -1065,6 +1156,10 @@ const ISO_CONTROLS = [
1065
1156
  id: "ISO/IEC 27001 · A.8.3",
1066
1157
  name: "Information access restriction",
1067
1158
  standard: ISO_27001,
1159
+ trace: (data) => ({
1160
+ rules: rulesOn(data.pack, (c) => c.startsWith("fs:")).map((r) => r.id),
1161
+ rows: decisionsOf(data).filter((e) => e.verdict === "deny" && e.capability?.startsWith("fs:"))
1162
+ }),
1068
1163
  asks: "Access to information is restricted according to the access-control policy.",
1069
1164
  evidence: (data) => {
1070
1165
  const rules = rulesOn(data.pack, (c) => c.startsWith("fs:"));
@@ -1086,6 +1181,10 @@ const ISO_CONTROLS = [
1086
1181
  id: "ISO/IEC 27001 · A.8.12",
1087
1182
  name: "Data leakage prevention",
1088
1183
  standard: ISO_27001,
1184
+ trace: (data) => ({
1185
+ rules: [...new Set([...rulesOn(data.pack, (c) => c === "net:fetch").map((r) => r.id), ...(data.pack?.rules ?? []).filter((r) => r.when.secrets !== void 0).map((r) => r.id)])],
1186
+ rows: decisionsOf(data).filter((e) => e.verdict === "deny" && (e.capability === "net:fetch" || e.secrets !== void 0 || e.phase === "output"))
1187
+ }),
1089
1188
  asks: "Leakage of sensitive information is prevented where it is processed or sent.",
1090
1189
  evidence: (data) => {
1091
1190
  const rules = [...rulesOn(data.pack, (c) => c === "net:fetch"), ...(data.pack?.rules ?? []).filter((r) => r.when.secrets !== void 0)];
@@ -1107,6 +1206,10 @@ const ISO_CONTROLS = [
1107
1206
  id: "ISO/IEC 27001 · A.8.15",
1108
1207
  name: "Logging",
1109
1208
  standard: ISO_27001,
1209
+ trace: (data) => ({
1210
+ rules: [],
1211
+ rows: data.entries
1212
+ }),
1110
1213
  asks: "Activities, exceptions and other relevant events are logged, and the logs are protected.",
1111
1214
  evidence: recorded
1112
1215
  },
@@ -1114,6 +1217,7 @@ const ISO_CONTROLS = [
1114
1217
  id: "ISO/IEC 27001 · A.8.16",
1115
1218
  name: "Monitoring activities",
1116
1219
  standard: ISO_27001,
1220
+ trace: heldAndRefused,
1117
1221
  asks: "Systems are monitored for anomalous behaviour, and potential incidents are evaluated.",
1118
1222
  evidence: monitored
1119
1223
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prae-cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Operator policy for agent harnesses: validate packs, explain a decision, test a pack against security invariants, verify a decision log, and provision a governed DSH profile.",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -33,7 +33,8 @@
33
33
  "dependencies": {
34
34
  "@modelcontextprotocol/sdk": "^1.30.0",
35
35
  "capmark": "^0.3.1",
36
- "dsh-overlay-check": "^0.4.0"
36
+ "dsh-overlay-check": "^0.4.0",
37
+ "yaml": "^2.8.3"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/node": "^22.10.0",
@@ -41,11 +42,11 @@
41
42
  "typescript": "^5.9.2",
42
43
  "vitest": "^3.2.4",
43
44
  "@prae/controls": "0.1.0",
44
- "@prae/extract": "0.1.0",
45
45
  "@prae/inspect": "0.1.0",
46
+ "@prae/extract": "0.1.0",
46
47
  "@prae/instructions": "0.1.0",
47
- "@prae/kernel": "0.1.0",
48
48
  "@prae/ledger": "0.1.0",
49
+ "@prae/kernel": "0.1.0",
49
50
  "@prae/tokens": "0.1.0"
50
51
  },
51
52
  "scripts": {
@@ -1,3 +0,0 @@
1
- import { _ as seal, a as signBytes, c as generateKeypair, d as verifyReceipt, f as verifySignature, g as hashEntry, h as chain, i as sealedKeypair, l as makeReceipt, m as canonical, n as readEntries, o as unsealPrivateKey, p as GENESIS, r as verifyEntries, s as fingerprint, t as Ledger, u as signEntry } from "./src-D7g4lvaY.js";
2
-
3
- export { GENESIS, Ledger, canonical, chain, fingerprint, generateKeypair, hashEntry, makeReceipt, readEntries, seal, sealedKeypair, signBytes, signEntry, unsealPrivateKey, verifyEntries, verifyReceipt, verifySignature };