prae-cli 0.9.2 → 0.10.0
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 +2 -6
- package/lib/{checkpoint-C_q_Rpa5.js → checkpoint-BqJ8xwky.js} +1 -1
- package/lib/cli-BcAJlB9B.js +8 -0
- package/lib/{cli-B_B6-bUZ.js → cli-DPldZ2ND.js} +259 -526
- package/lib/docs/pilot-claude-code.md +191 -0
- package/lib/{evaluate-BqLxhYiP.js → evaluate-DhoGQw0Z.js} +23 -1
- package/lib/frameworks-BgZHGKiH.js +118 -0
- package/lib/hook-Gll2spTP.js +695 -0
- package/lib/index.js +6 -4
- package/lib/init-DRd-IRR6.js +266 -0
- package/lib/{instructions-ya0LVnZy.js → instructions-DihPiiC2.js} +2 -1
- package/lib/{mcp-BCXbpqu4.js → mcp-D521EoP6.js} +2 -2
- package/lib/pack-h5n43S0F.js +448 -0
- package/lib/playground.js +4 -0
- package/lib/{receipt-check-DQK_gVPc.js → receipt-check-CUuEjGHL.js} +1 -1
- package/lib/{src-LA7NdLKb.js → src-Cy-Acrgj.js} +104 -25
- package/lib/{src-CwBEFhww.js → src-DMoYsdfo.js} +1 -1
- package/lib/src-wG36kT6K.js +3 -0
- package/lib/{test-run-CCDgJ0Dp.js → test-run-C8dHjIE2.js} +1 -1
- package/lib/views-CUcixte0.js +1396 -0
- package/package.json +4 -4
- package/lib/src-7X6dO7gC.js +0 -3
- package/lib/views-D4UEfbzt.js +0 -1505
- /package/lib/{ocsf-DhVGbBUw.js → ocsf-DF9cMiQJ.js} +0 -0
- /package/lib/{src-CBGZVvt7.js → src-BlmsTGdY.js} +0 -0
- /package/lib/{src-B25SHWcz.js → src-BuaOiL2v.js} +0 -0
- /package/lib/{src-d4c_n6wd.js → src-CDYyZyeU.js} +0 -0
|
@@ -1,456 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
|
|
5
8
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
9
|
import { basename, dirname, extname, join, normalize, resolve, sep } from "node:path";
|
|
7
10
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { parse } from "yaml";
|
|
9
11
|
import { homedir } from "node:os";
|
|
12
|
+
import { parse } from "yaml";
|
|
10
13
|
import { composePatchFile, preflightOps, revisionOf, takeSnapshot } from "dsh-overlay-check";
|
|
11
14
|
import { createServer } from "node:http";
|
|
12
15
|
|
|
13
|
-
//#region ../kernel/src/pack.ts
|
|
14
|
-
function isObject(value) {
|
|
15
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
-
}
|
|
17
|
-
var Check = class {
|
|
18
|
-
errors = [];
|
|
19
|
-
fail(path, message) {
|
|
20
|
-
this.errors.push({
|
|
21
|
-
path,
|
|
22
|
-
message
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
/** Reject any key the shape does not define. */
|
|
26
|
-
keys(path, value, allowed) {
|
|
27
|
-
for (const key of Object.keys(value)) if (!allowed.includes(key)) this.fail(`${path}.${key}`, `unknown field \`${key}\`; expected one of ${allowed.map((k) => `\`${k}\``).join(", ")}`);
|
|
28
|
-
}
|
|
29
|
-
text(path, value) {
|
|
30
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
31
|
-
this.fail(path, "expected a non-empty string");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
/** A field that accepts one name or a list of them. */
|
|
37
|
-
names(path, value) {
|
|
38
|
-
if (typeof value === "string") return value;
|
|
39
|
-
if (Array.isArray(value)) {
|
|
40
|
-
const out = [];
|
|
41
|
-
value.forEach((entry, index) => {
|
|
42
|
-
const one = this.text(`${path}[${index}]`, entry);
|
|
43
|
-
if (one !== void 0) out.push(one);
|
|
44
|
-
});
|
|
45
|
-
return out;
|
|
46
|
-
}
|
|
47
|
-
this.fail(path, "expected a string or an array of strings");
|
|
48
|
-
}
|
|
49
|
-
globs(path, value) {
|
|
50
|
-
if (!Array.isArray(value)) {
|
|
51
|
-
this.fail(path, "expected an array of path globs");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const out = [];
|
|
55
|
-
value.forEach((entry, index) => {
|
|
56
|
-
const one = this.text(`${path}[${index}]`, entry);
|
|
57
|
-
if (one !== void 0) out.push(one);
|
|
58
|
-
});
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
number(path, value) {
|
|
62
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
63
|
-
this.fail(path, "expected a finite number");
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
return value;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
function readCommandMatch(check$1, path, value) {
|
|
70
|
-
if (!isObject(value)) {
|
|
71
|
-
check$1.fail(path, "expected an object");
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
check$1.keys(path, value, [
|
|
75
|
-
"name",
|
|
76
|
-
"token",
|
|
77
|
-
"anyToken",
|
|
78
|
-
"pipedTo",
|
|
79
|
-
"pipeline"
|
|
80
|
-
]);
|
|
81
|
-
if (value.name === void 0 && value.token === void 0 && value.anyToken === void 0 && value.pipedTo === void 0 && value.pipeline === void 0) check$1.fail(path, "expected `name`, `token`, `anyToken`, `pipedTo` or `pipeline`; a command matcher with none matches nothing");
|
|
82
|
-
const out = {};
|
|
83
|
-
if (value.token !== void 0) {
|
|
84
|
-
const tokens = check$1.names(`${path}.token`, value.token);
|
|
85
|
-
if (tokens !== void 0) out.token = tokens;
|
|
86
|
-
}
|
|
87
|
-
if (value.name !== void 0) {
|
|
88
|
-
const names = check$1.names(`${path}.name`, value.name);
|
|
89
|
-
if (names !== void 0) out.name = names;
|
|
90
|
-
}
|
|
91
|
-
if (value.anyToken !== void 0) {
|
|
92
|
-
const tokens = check$1.names(`${path}.anyToken`, value.anyToken);
|
|
93
|
-
if (tokens !== void 0) out.anyToken = tokens;
|
|
94
|
-
}
|
|
95
|
-
if (value.pipedTo !== void 0) {
|
|
96
|
-
const pipes = check$1.names(`${path}.pipedTo`, value.pipedTo);
|
|
97
|
-
if (pipes !== void 0) out.pipedTo = pipes;
|
|
98
|
-
}
|
|
99
|
-
if (value.pipeline !== void 0) {
|
|
100
|
-
const shapes = check$1.names(`${path}.pipeline`, value.pipeline);
|
|
101
|
-
if (shapes !== void 0) out.pipeline = shapes;
|
|
102
|
-
}
|
|
103
|
-
return out;
|
|
104
|
-
}
|
|
105
|
-
function readPathMatch(check$1, path, value) {
|
|
106
|
-
if (!isObject(value)) {
|
|
107
|
-
check$1.fail(path, "expected an object");
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
check$1.keys(path, value, [
|
|
111
|
-
"arg",
|
|
112
|
-
"outside",
|
|
113
|
-
"inside"
|
|
114
|
-
]);
|
|
115
|
-
if (value.outside === void 0 && value.inside === void 0) check$1.fail(path, "expected `inside` or `outside`; a path matcher with neither matches nothing");
|
|
116
|
-
const out = {};
|
|
117
|
-
if (value.arg !== void 0) {
|
|
118
|
-
const arg = check$1.text(`${path}.arg`, value.arg);
|
|
119
|
-
if (arg !== void 0) out.arg = arg;
|
|
120
|
-
}
|
|
121
|
-
if (value.outside !== void 0) {
|
|
122
|
-
const globs = check$1.globs(`${path}.outside`, value.outside);
|
|
123
|
-
if (globs !== void 0) out.outside = globs;
|
|
124
|
-
}
|
|
125
|
-
if (value.inside !== void 0) {
|
|
126
|
-
const globs = check$1.globs(`${path}.inside`, value.inside);
|
|
127
|
-
if (globs !== void 0) out.inside = globs;
|
|
128
|
-
}
|
|
129
|
-
return out;
|
|
130
|
-
}
|
|
131
|
-
function readHostMatch(check$1, path, value) {
|
|
132
|
-
if (!isObject(value)) {
|
|
133
|
-
check$1.fail(path, "expected an object");
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
check$1.keys(path, value, [
|
|
137
|
-
"arg",
|
|
138
|
-
"in",
|
|
139
|
-
"notIn"
|
|
140
|
-
]);
|
|
141
|
-
if (value.in === void 0 && value.notIn === void 0) check$1.fail(path, "expected `in` or `notIn`; a host matcher with neither matches nothing");
|
|
142
|
-
const out = {};
|
|
143
|
-
if (value.arg !== void 0) {
|
|
144
|
-
const arg = check$1.text(`${path}.arg`, value.arg);
|
|
145
|
-
if (arg !== void 0) out.arg = arg;
|
|
146
|
-
}
|
|
147
|
-
if (value.in !== void 0) {
|
|
148
|
-
const list = check$1.globs(`${path}.in`, value.in);
|
|
149
|
-
if (list !== void 0) out.in = list;
|
|
150
|
-
}
|
|
151
|
-
if (value.notIn !== void 0) {
|
|
152
|
-
const list = check$1.globs(`${path}.notIn`, value.notIn);
|
|
153
|
-
if (list !== void 0) out.notIn = list;
|
|
154
|
-
}
|
|
155
|
-
return out;
|
|
156
|
-
}
|
|
157
|
-
function readArgMatch(check$1, path, value) {
|
|
158
|
-
if (!isObject(value)) {
|
|
159
|
-
check$1.fail(path, "expected an object");
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
check$1.keys(path, value, [
|
|
163
|
-
"name",
|
|
164
|
-
"above",
|
|
165
|
-
"below",
|
|
166
|
-
"equals",
|
|
167
|
-
"oneOf",
|
|
168
|
-
"present"
|
|
169
|
-
]);
|
|
170
|
-
const name = check$1.text(`${path}.name`, value.name);
|
|
171
|
-
if (name === void 0) return void 0;
|
|
172
|
-
const out = { name };
|
|
173
|
-
if (value.above !== void 0) {
|
|
174
|
-
const n = check$1.number(`${path}.above`, value.above);
|
|
175
|
-
if (n !== void 0) out.above = n;
|
|
176
|
-
}
|
|
177
|
-
if (value.below !== void 0) {
|
|
178
|
-
const n = check$1.number(`${path}.below`, value.below);
|
|
179
|
-
if (n !== void 0) out.below = n;
|
|
180
|
-
}
|
|
181
|
-
if (value.equals !== void 0) out.equals = value.equals;
|
|
182
|
-
if (value.oneOf !== void 0) if (!Array.isArray(value.oneOf)) check$1.fail(`${path}.oneOf`, "expected an array");
|
|
183
|
-
else out.oneOf = value.oneOf;
|
|
184
|
-
if (value.present !== void 0) if (typeof value.present !== "boolean") check$1.fail(`${path}.present`, "expected a boolean");
|
|
185
|
-
else out.present = value.present;
|
|
186
|
-
return out;
|
|
187
|
-
}
|
|
188
|
-
function readSecretsMatch(check$1, path, value) {
|
|
189
|
-
if (!isObject(value)) {
|
|
190
|
-
check$1.fail(path, "expected an object");
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
check$1.keys(path, value, [
|
|
194
|
-
"types",
|
|
195
|
-
"minConfidence",
|
|
196
|
-
"atLeast"
|
|
197
|
-
]);
|
|
198
|
-
const out = {};
|
|
199
|
-
if (value.types !== void 0) {
|
|
200
|
-
const types = check$1.globs(`${path}.types`, value.types);
|
|
201
|
-
if (types !== void 0) out.types = types;
|
|
202
|
-
}
|
|
203
|
-
if (value.minConfidence !== void 0) {
|
|
204
|
-
const n = check$1.number(`${path}.minConfidence`, value.minConfidence);
|
|
205
|
-
if (n !== void 0) if (n < 0 || n > 1) check$1.fail(`${path}.minConfidence`, "expected a number between 0 and 1");
|
|
206
|
-
else out.minConfidence = n;
|
|
207
|
-
}
|
|
208
|
-
if (value.atLeast !== void 0) {
|
|
209
|
-
const n = check$1.number(`${path}.atLeast`, value.atLeast);
|
|
210
|
-
if (n !== void 0) if (!Number.isInteger(n) || n < 1) check$1.fail(`${path}.atLeast`, "expected an integer of at least 1");
|
|
211
|
-
else out.atLeast = n;
|
|
212
|
-
}
|
|
213
|
-
return out;
|
|
214
|
-
}
|
|
215
|
-
function readMatch(check$1, path, value) {
|
|
216
|
-
if (!isObject(value)) {
|
|
217
|
-
check$1.fail(path, "expected an object");
|
|
218
|
-
return {};
|
|
219
|
-
}
|
|
220
|
-
check$1.keys(path, value, [
|
|
221
|
-
"tool",
|
|
222
|
-
"capability",
|
|
223
|
-
"plugin",
|
|
224
|
-
"agent",
|
|
225
|
-
"command",
|
|
226
|
-
"path",
|
|
227
|
-
"host",
|
|
228
|
-
"arg",
|
|
229
|
-
"count",
|
|
230
|
-
"secrets"
|
|
231
|
-
]);
|
|
232
|
-
const out = {};
|
|
233
|
-
for (const key of [
|
|
234
|
-
"tool",
|
|
235
|
-
"capability",
|
|
236
|
-
"plugin",
|
|
237
|
-
"agent"
|
|
238
|
-
]) if (value[key] !== void 0) {
|
|
239
|
-
const names = check$1.names(`${path}.${key}`, value[key]);
|
|
240
|
-
if (names !== void 0) out[key] = names;
|
|
241
|
-
}
|
|
242
|
-
if (value.command !== void 0) {
|
|
243
|
-
const match = readCommandMatch(check$1, `${path}.command`, value.command);
|
|
244
|
-
if (match !== void 0) out.command = match;
|
|
245
|
-
}
|
|
246
|
-
if (value.path !== void 0) {
|
|
247
|
-
const match = readPathMatch(check$1, `${path}.path`, value.path);
|
|
248
|
-
if (match !== void 0) out.path = match;
|
|
249
|
-
}
|
|
250
|
-
if (value.host !== void 0) {
|
|
251
|
-
const match = readHostMatch(check$1, `${path}.host`, value.host);
|
|
252
|
-
if (match !== void 0) out.host = match;
|
|
253
|
-
}
|
|
254
|
-
if (value.arg !== void 0) if (!Array.isArray(value.arg)) check$1.fail(`${path}.arg`, "expected an array of argument matchers");
|
|
255
|
-
else {
|
|
256
|
-
const list = [];
|
|
257
|
-
value.arg.forEach((entry, index) => {
|
|
258
|
-
const one = readArgMatch(check$1, `${path}.arg[${index}]`, entry);
|
|
259
|
-
if (one !== void 0) list.push(one);
|
|
260
|
-
});
|
|
261
|
-
out.arg = list;
|
|
262
|
-
}
|
|
263
|
-
if (value.count !== void 0) if (!isObject(value.count)) check$1.fail(`${path}.count`, "expected an object");
|
|
264
|
-
else {
|
|
265
|
-
check$1.keys(`${path}.count`, value.count, ["above"]);
|
|
266
|
-
const above = check$1.number(`${path}.count.above`, value.count.above);
|
|
267
|
-
if (above !== void 0) out.count = { above };
|
|
268
|
-
}
|
|
269
|
-
if (value.secrets !== void 0) {
|
|
270
|
-
const match = readSecretsMatch(check$1, `${path}.secrets`, value.secrets);
|
|
271
|
-
if (match !== void 0) out.secrets = match;
|
|
272
|
-
}
|
|
273
|
-
return out;
|
|
274
|
-
}
|
|
275
|
-
function readEffect(check$1, path, value) {
|
|
276
|
-
if (!isObject(value)) {
|
|
277
|
-
check$1.fail(path, "expected an object");
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const kind = value.kind === "hold" ? "ask" : value.kind;
|
|
281
|
-
if (kind !== "deny" && kind !== "ask" && kind !== "correct" && kind !== "warn" && kind !== "allow") {
|
|
282
|
-
check$1.fail(`${path}.kind`, "expected `allow`, `deny`, `hold` (or `ask`), `correct`, or `warn`");
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
check$1.keys(path, value, kind === "correct" ? [
|
|
286
|
-
"kind",
|
|
287
|
-
"set",
|
|
288
|
-
"because"
|
|
289
|
-
] : ["kind", "because"]);
|
|
290
|
-
const because = check$1.text(`${path}.because`, value.because);
|
|
291
|
-
if (because === void 0) return void 0;
|
|
292
|
-
if (kind === "correct") {
|
|
293
|
-
if (!isObject(value.set) || Object.keys(value.set).length === 0) {
|
|
294
|
-
check$1.fail(`${path}.set`, "expected a non-empty object of arguments to set");
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
return {
|
|
298
|
-
kind,
|
|
299
|
-
set: value.set,
|
|
300
|
-
because
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
return {
|
|
304
|
-
kind,
|
|
305
|
-
because
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
function readRule(check$1, path, value) {
|
|
309
|
-
if (!isObject(value)) {
|
|
310
|
-
check$1.fail(path, "expected an object");
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
check$1.keys(path, value, [
|
|
314
|
-
"id",
|
|
315
|
-
"title",
|
|
316
|
-
"description",
|
|
317
|
-
"when",
|
|
318
|
-
"effect",
|
|
319
|
-
"frameworks"
|
|
320
|
-
]);
|
|
321
|
-
const id = check$1.text(`${path}.id`, value.id);
|
|
322
|
-
const effect = readEffect(check$1, `${path}.effect`, value.effect);
|
|
323
|
-
if (value.when === void 0) check$1.fail(`${path}.when`, "expected an object");
|
|
324
|
-
const when = readMatch(check$1, `${path}.when`, value.when ?? {});
|
|
325
|
-
if (id === void 0 || effect === void 0) return void 0;
|
|
326
|
-
const rule = {
|
|
327
|
-
id,
|
|
328
|
-
when,
|
|
329
|
-
effect
|
|
330
|
-
};
|
|
331
|
-
if (value.title !== void 0) {
|
|
332
|
-
const title = check$1.text(`${path}.title`, value.title);
|
|
333
|
-
if (title !== void 0) rule.title = title;
|
|
334
|
-
}
|
|
335
|
-
if (value.description !== void 0) {
|
|
336
|
-
const description = check$1.text(`${path}.description`, value.description);
|
|
337
|
-
if (description !== void 0) rule.description = description;
|
|
338
|
-
}
|
|
339
|
-
if (value.frameworks !== void 0) {
|
|
340
|
-
const frameworks = readFrameworks(check$1, `${path}.frameworks`, value.frameworks);
|
|
341
|
-
if (frameworks !== void 0) rule.frameworks = frameworks;
|
|
342
|
-
}
|
|
343
|
-
return rule;
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* A citation names a framework edition and controls the registry knows. A
|
|
347
|
-
* typo here would be a rule that claims a mapping no view can render, so it
|
|
348
|
-
* is a pack error rather than an ignored key — the same line the rest of the
|
|
349
|
-
* parser takes.
|
|
350
|
-
*/
|
|
351
|
-
function readFrameworks(check$1, path, value) {
|
|
352
|
-
if (!isObject(value)) {
|
|
353
|
-
check$1.fail(path, "expected an object keyed by framework id");
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
const out = {};
|
|
357
|
-
for (const [framework, controls] of Object.entries(value)) {
|
|
358
|
-
const known = FRAMEWORKS[framework];
|
|
359
|
-
if (known === void 0) {
|
|
360
|
-
check$1.fail(`${path}.${framework}`, `unknown framework; expected one of ${Object.keys(FRAMEWORKS).map((k) => `\`${k}\``).join(", ")}`);
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
if (!Array.isArray(controls) || !controls.every((c) => typeof c === "string")) {
|
|
364
|
-
check$1.fail(`${path}.${framework}`, "expected an array of control ids");
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
const ids = controls;
|
|
368
|
-
for (const control of ids) if (known.controls[control] === void 0) check$1.fail(`${path}.${framework}`, `unknown control \`${control}\` in ${known.name}`);
|
|
369
|
-
if (ids.length > 0) out[framework] = [...new Set(ids)];
|
|
370
|
-
}
|
|
371
|
-
return out;
|
|
372
|
-
}
|
|
373
|
-
/** Parse and validate a pack. Never throws; every problem lands in `errors`. */
|
|
374
|
-
function parsePack(source) {
|
|
375
|
-
let raw;
|
|
376
|
-
try {
|
|
377
|
-
raw = JSON.parse(source);
|
|
378
|
-
} catch (error) {
|
|
379
|
-
return {
|
|
380
|
-
ok: false,
|
|
381
|
-
errors: [{
|
|
382
|
-
path: "",
|
|
383
|
-
message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
384
|
-
}]
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
const check$1 = new Check();
|
|
388
|
-
if (!isObject(raw)) return {
|
|
389
|
-
ok: false,
|
|
390
|
-
errors: [{
|
|
391
|
-
path: "",
|
|
392
|
-
message: "expected an object"
|
|
393
|
-
}]
|
|
394
|
-
};
|
|
395
|
-
check$1.keys("", raw, [
|
|
396
|
-
"prae",
|
|
397
|
-
"pack",
|
|
398
|
-
"version",
|
|
399
|
-
"description",
|
|
400
|
-
"owner",
|
|
401
|
-
"baseline",
|
|
402
|
-
"rules"
|
|
403
|
-
]);
|
|
404
|
-
if (raw.prae !== "0.1") check$1.fail(".prae", "expected `\"0.1\"`; this is the only pack version this build reads");
|
|
405
|
-
const name = check$1.text(".pack", raw.pack);
|
|
406
|
-
const version = check$1.text(".version", raw.version);
|
|
407
|
-
const description = raw.description === void 0 ? void 0 : check$1.text(".description", raw.description);
|
|
408
|
-
const owner = raw.owner === void 0 ? void 0 : check$1.text(".owner", raw.owner);
|
|
409
|
-
let baseline;
|
|
410
|
-
if (raw.baseline !== void 0) if (!isObject(raw.baseline)) check$1.fail(".baseline", "expected an object");
|
|
411
|
-
else {
|
|
412
|
-
check$1.keys(".baseline", raw.baseline, ["capabilities"]);
|
|
413
|
-
const caps = check$1.globs(".baseline.capabilities", raw.baseline.capabilities);
|
|
414
|
-
if (caps !== void 0) baseline = { capabilities: caps };
|
|
415
|
-
}
|
|
416
|
-
const rules = [];
|
|
417
|
-
if (!Array.isArray(raw.rules)) check$1.fail(".rules", "expected an array");
|
|
418
|
-
else {
|
|
419
|
-
const seen = /* @__PURE__ */ new Set();
|
|
420
|
-
raw.rules.forEach((entry, index) => {
|
|
421
|
-
const rule = readRule(check$1, `rules[${index}]`, entry);
|
|
422
|
-
if (rule === void 0) return;
|
|
423
|
-
if (seen.has(rule.id)) {
|
|
424
|
-
check$1.fail(`rules[${index}].id`, `duplicate rule id \`${rule.id}\``);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
seen.add(rule.id);
|
|
428
|
-
rules.push(rule);
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
if (check$1.errors.length > 0) return {
|
|
432
|
-
ok: false,
|
|
433
|
-
errors: check$1.errors
|
|
434
|
-
};
|
|
435
|
-
if (name === void 0 || version === void 0) return {
|
|
436
|
-
ok: false,
|
|
437
|
-
errors: check$1.errors
|
|
438
|
-
};
|
|
439
|
-
return {
|
|
440
|
-
ok: true,
|
|
441
|
-
pack: {
|
|
442
|
-
prae: "0.1",
|
|
443
|
-
pack: name,
|
|
444
|
-
version,
|
|
445
|
-
...description === void 0 ? {} : { description },
|
|
446
|
-
...owner === void 0 ? {} : { owner },
|
|
447
|
-
...baseline === void 0 ? {} : { baseline },
|
|
448
|
-
rules
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
//#endregion
|
|
454
16
|
//#region ../dsh/src/chain.ts
|
|
455
17
|
const STAGES = [
|
|
456
18
|
{
|
|
@@ -707,14 +269,14 @@ function readPackFile(file, policyKey, origin = "workspace") {
|
|
|
707
269
|
reason: `invalid YAML: ${e instanceof Error ? e.message : String(e)}`
|
|
708
270
|
};
|
|
709
271
|
}
|
|
710
|
-
const parsed = parsePack(json);
|
|
711
|
-
if (!parsed.ok) return {
|
|
272
|
+
const parsed$1 = parsePack(json);
|
|
273
|
+
if (!parsed$1.ok) return {
|
|
712
274
|
file,
|
|
713
|
-
reason: parsed.errors.map((e) => `${e.path || "<root>"}: ${e.message}`).join("; ")
|
|
275
|
+
reason: parsed$1.errors.map((e) => `${e.path || "<root>"}: ${e.message}`).join("; ")
|
|
714
276
|
};
|
|
715
277
|
return {
|
|
716
278
|
file,
|
|
717
|
-
pack: parsed.pack,
|
|
279
|
+
pack: parsed$1.pack,
|
|
718
280
|
origin,
|
|
719
281
|
sha256,
|
|
720
282
|
...policyKey === void 0 ? {} : { signedBy: fingerprint(policyKey) }
|
|
@@ -809,6 +371,7 @@ function merge(packs, rejected) {
|
|
|
809
371
|
};
|
|
810
372
|
const baseline = packs.find((p) => p.pack.baseline !== void 0)?.pack.baseline;
|
|
811
373
|
const only = packs.length === 1 ? packs[0] : void 0;
|
|
374
|
+
const expires = packs.map((p) => p.pack.expires_at).filter((e) => e !== void 0).sort((a, b) => Date.parse(a) - Date.parse(b))[0];
|
|
812
375
|
return {
|
|
813
376
|
packs,
|
|
814
377
|
rejected,
|
|
@@ -818,6 +381,7 @@ function merge(packs, rejected) {
|
|
|
818
381
|
pack: only?.pack.pack ?? packs.map((p) => p.pack.pack).join("+"),
|
|
819
382
|
version: only?.pack.version ?? createHash("sha256").update(packs.map((p) => p.sha256).join("")).digest("hex").slice(0, 12),
|
|
820
383
|
...baseline === void 0 ? {} : { baseline },
|
|
384
|
+
...expires === void 0 ? {} : { expires_at: expires },
|
|
821
385
|
rules: packs.flatMap((p) => p.pack.rules)
|
|
822
386
|
}
|
|
823
387
|
};
|
|
@@ -843,7 +407,7 @@ function row(entry, pack) {
|
|
|
843
407
|
<div class="call">${esc(call)}</div>
|
|
844
408
|
<div class="actor">${esc(actor)}</div>
|
|
845
409
|
<div class="clause">${esc(entry.rule ?? "—")}${cited}</div>
|
|
846
|
-
<div class="verdict v-${esc(entry.verdict)}">${esc(VERDICT_LABEL$1[entry.verdict] ?? entry.verdict)}</div>
|
|
410
|
+
<div class="verdict v-${esc(entry.verdict)}">${esc(VERDICT_LABEL$1[entry.verdict] ?? entry.verdict)}${entry.shadow === true ? " · SHADOW" : ""}</div>
|
|
847
411
|
<div class="seal">${esc(seal(entry))}</div>
|
|
848
412
|
</div>`;
|
|
849
413
|
}
|
|
@@ -851,12 +415,14 @@ function renderLedgerView(entries, verification, options) {
|
|
|
851
415
|
const counts = {};
|
|
852
416
|
const packs = /* @__PURE__ */ new Set();
|
|
853
417
|
const sessions = /* @__PURE__ */ new Set();
|
|
418
|
+
let shadowed = 0;
|
|
854
419
|
for (const entry of entries) {
|
|
855
|
-
|
|
420
|
+
if (entry.shadow === true) shadowed += 1;
|
|
421
|
+
else counts[entry.verdict] = (counts[entry.verdict] ?? 0) + 1;
|
|
856
422
|
packs.add(`${entry.pack.name} ${entry.pack.version}`);
|
|
857
423
|
if (entry.session !== void 0) sessions.add(entry.session);
|
|
858
424
|
}
|
|
859
|
-
const held = entries.filter((e) => e.verdict === "ask");
|
|
425
|
+
const held = entries.filter((e) => e.verdict === "ask" && e.shadow !== true);
|
|
860
426
|
const pack = [...packs][0] ?? "—";
|
|
861
427
|
const now = Date.parse(options.generatedAt);
|
|
862
428
|
const chainOk = verification.ok;
|
|
@@ -866,6 +432,7 @@ function renderLedgerView(entries, verification, options) {
|
|
|
866
432
|
<div class="meta">${esc([
|
|
867
433
|
`${sessions.size || 1} session${sessions.size === 1 ? "" : "s"}`,
|
|
868
434
|
`${entries.length.toLocaleString("en-US")} decisions`,
|
|
435
|
+
...shadowed === 0 ? [] : [`${shadowed} in shadow mode (recorded, not enforced)`],
|
|
869
436
|
options.retention === void 0 ? "retention local" : `retention ${options.retention}`
|
|
870
437
|
].join(" · "))}</div>
|
|
871
438
|
</div>
|
|
@@ -1201,16 +768,60 @@ function jsonOrNull(path) {
|
|
|
1201
768
|
return null;
|
|
1202
769
|
}
|
|
1203
770
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
const
|
|
1207
|
-
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
771
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
772
|
+
function parseLines(text, into) {
|
|
773
|
+
for (const line of text.split("\n")) {
|
|
1208
774
|
if (line.trim() === "") continue;
|
|
1209
775
|
try {
|
|
1210
|
-
|
|
776
|
+
into.push(JSON.parse(line));
|
|
1211
777
|
} catch {}
|
|
1212
778
|
}
|
|
1213
|
-
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* The ledger's rows. A ledger is append-only, so the parse is kept and only
|
|
782
|
+
* the bytes appended since are read: a long demo grows it to tens of
|
|
783
|
+
* thousands of rows, and every endpoint the console polls reads it — parsed
|
|
784
|
+
* whole each time, that kept the server too busy to answer anyone. A file
|
|
785
|
+
* that shrank or was replaced is read again from the start. A half-written
|
|
786
|
+
* last line is left unread until its newline arrives.
|
|
787
|
+
*/
|
|
788
|
+
function readLedger(path) {
|
|
789
|
+
if (!existsSync(path)) {
|
|
790
|
+
parsed.delete(path);
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
793
|
+
const stat$1 = statSync(path);
|
|
794
|
+
let entry = parsed.get(path);
|
|
795
|
+
if (entry === void 0 || entry.ino !== stat$1.ino || stat$1.size < entry.size) {
|
|
796
|
+
entry = {
|
|
797
|
+
ino: stat$1.ino,
|
|
798
|
+
size: 0,
|
|
799
|
+
mtimeMs: 0,
|
|
800
|
+
offset: 0,
|
|
801
|
+
rows: []
|
|
802
|
+
};
|
|
803
|
+
parsed.set(path, entry);
|
|
804
|
+
}
|
|
805
|
+
if (stat$1.size !== entry.size || stat$1.mtimeMs !== entry.mtimeMs) {
|
|
806
|
+
const length = stat$1.size - entry.offset;
|
|
807
|
+
if (length > 0) {
|
|
808
|
+
const buf = Buffer.alloc(length);
|
|
809
|
+
const fd = openSync(path, "r");
|
|
810
|
+
try {
|
|
811
|
+
readSync(fd, buf, 0, length, entry.offset);
|
|
812
|
+
} finally {
|
|
813
|
+
closeSync(fd);
|
|
814
|
+
}
|
|
815
|
+
const end = buf.lastIndexOf(10);
|
|
816
|
+
if (end >= 0) {
|
|
817
|
+
parseLines(buf.subarray(0, end).toString("utf8"), entry.rows);
|
|
818
|
+
entry.offset += end + 1;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
entry.size = stat$1.size;
|
|
822
|
+
entry.mtimeMs = stat$1.mtimeMs;
|
|
823
|
+
}
|
|
824
|
+
return entry.rows.slice();
|
|
1214
825
|
}
|
|
1215
826
|
/** The defaults the demo writes; a caller with other names overrides them. */
|
|
1216
827
|
const DEFAULT_FILES = {
|
|
@@ -1229,7 +840,8 @@ function readRecord(dir, files) {
|
|
|
1229
840
|
discovery: jsonOrNull(resolve(dir, files.discovery)),
|
|
1230
841
|
report: jsonOrNull(resolve(dir, files.report)),
|
|
1231
842
|
guardians: jsonOrNull(resolve(dir, files.guardians)),
|
|
1232
|
-
pack: jsonOrNull(resolve(dir, files.pack))
|
|
843
|
+
pack: jsonOrNull(resolve(dir, files.pack)),
|
|
844
|
+
crownJewel: jsonOrNull(resolve(dir, "crown-jewel.json"))
|
|
1233
845
|
};
|
|
1234
846
|
}
|
|
1235
847
|
/** The red-team campaign and its runs, as the runner wrote them; empty when none. */
|
|
@@ -1238,15 +850,19 @@ function readRedteam(dir, files) {
|
|
|
1238
850
|
if (!existsSync(root)) return {
|
|
1239
851
|
campaign: null,
|
|
1240
852
|
runs: [],
|
|
1241
|
-
steering: []
|
|
853
|
+
steering: [],
|
|
854
|
+
history: []
|
|
1242
855
|
};
|
|
1243
856
|
const runs = readdirSync(root).filter((n) => n.endsWith(".json")).sort().filter((n) => n !== "campaign.json").map((n) => jsonOrNull(resolve(root, n))).filter((r) => r !== null);
|
|
1244
857
|
const steeringDir = resolve(root, "steering");
|
|
1245
858
|
const steering = existsSync(steeringDir) ? readdirSync(steeringDir).filter((n) => n.endsWith(".json")).sort().map((n) => jsonOrNull(resolve(steeringDir, n))).filter((r) => r !== null && typeof r === "object" && "objective" in r) : [];
|
|
859
|
+
const historyDir = resolve(steeringDir, "history");
|
|
860
|
+
const history = existsSync(historyDir) ? readdirSync(historyDir).filter((n) => n.endsWith(".json")).sort().reverse().map((n) => jsonOrNull(resolve(historyDir, n))).filter((r) => r !== null) : [];
|
|
1246
861
|
return {
|
|
1247
862
|
campaign: jsonOrNull(resolve(root, "campaign.json")),
|
|
1248
863
|
runs,
|
|
1249
|
-
steering
|
|
864
|
+
steering,
|
|
865
|
+
history
|
|
1250
866
|
};
|
|
1251
867
|
}
|
|
1252
868
|
|
|
@@ -1647,6 +1263,23 @@ function verifyRecord(record, tamper, repair = false) {
|
|
|
1647
1263
|
repaired: false,
|
|
1648
1264
|
signatures: null
|
|
1649
1265
|
};
|
|
1266
|
+
if (tamper === void 0) {
|
|
1267
|
+
const keyPath$1 = resolve(dir, files.publicKey);
|
|
1268
|
+
const publicKey = existsSync(keyPath$1) ? readFileSync(keyPath$1, "utf8") : void 0;
|
|
1269
|
+
const v = verifyGrowing(path, publicKey);
|
|
1270
|
+
return {
|
|
1271
|
+
ok: v.ok,
|
|
1272
|
+
entries: v.entries,
|
|
1273
|
+
problems: [...v.problems],
|
|
1274
|
+
tampered: null,
|
|
1275
|
+
repaired: false,
|
|
1276
|
+
signatures: v.signatures === null || publicKey === void 0 ? null : {
|
|
1277
|
+
checked: v.signatures.checked,
|
|
1278
|
+
failed: [...v.signatures.failed],
|
|
1279
|
+
fingerprint: fingerprint(publicKey)
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1650
1283
|
let text = readFileSync(path, "utf8");
|
|
1651
1284
|
let tampered = null;
|
|
1652
1285
|
if (tamper !== void 0) {
|
|
@@ -1754,19 +1387,19 @@ function packsOf(record) {
|
|
|
1754
1387
|
function complianceOf(record) {
|
|
1755
1388
|
const { dir, files } = record;
|
|
1756
1389
|
const ledgerPath = resolve(dir, files.ledger);
|
|
1757
|
-
const
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
}
|
|
1390
|
+
const entries = readLedger(ledgerPath);
|
|
1391
|
+
const grown = existsSync(ledgerPath) ? verifyGrowing(ledgerPath) : void 0;
|
|
1392
|
+
const verification = grown === void 0 ? verifyEntries("") : {
|
|
1393
|
+
ok: grown.ok,
|
|
1394
|
+
entries: grown.entries,
|
|
1395
|
+
problems: grown.problems
|
|
1396
|
+
};
|
|
1764
1397
|
const { set } = packSetOf(record);
|
|
1765
1398
|
const pack = set.packs.length > 0 ? set.effective : void 0;
|
|
1766
1399
|
const data = {
|
|
1767
1400
|
...pack === void 0 ? {} : { pack },
|
|
1768
1401
|
entries,
|
|
1769
|
-
verification
|
|
1402
|
+
verification,
|
|
1770
1403
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1771
1404
|
};
|
|
1772
1405
|
return {
|
|
@@ -1789,6 +1422,23 @@ function complianceOf(record) {
|
|
|
1789
1422
|
};
|
|
1790
1423
|
}
|
|
1791
1424
|
/**
|
|
1425
|
+
* The API's version is prae-cli's: every `/api/*` response names it in
|
|
1426
|
+
* `X-PRAE-API-Version`, so a client can tell which contract answered. The
|
|
1427
|
+
* contract follows the package's semver — a breaking change to a route bumps
|
|
1428
|
+
* the major — and a route on its way out carries `Deprecation` and `Sunset`
|
|
1429
|
+
* (RFC 9745 / RFC 8594) for at least one minor release before it goes.
|
|
1430
|
+
*/
|
|
1431
|
+
/**
|
|
1432
|
+
* The most rows `/api/record` sends: the newest ones. The console keeps the
|
|
1433
|
+
* same window (apps/console/src/data/hooks.ts); `trimmed` counts the rest.
|
|
1434
|
+
*/
|
|
1435
|
+
const RECORD_WINDOW = 25e3;
|
|
1436
|
+
const API_VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
1437
|
+
/** Name the contract on an API response; call before the status is written. */
|
|
1438
|
+
function apiVersion(res) {
|
|
1439
|
+
res.setHeader("x-prae-api-version", API_VERSION);
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1792
1442
|
* The record over HTTP, for the Command Center (apps/console): the same
|
|
1793
1443
|
* files `prae console` renders, as JSON, plus one event stream that says
|
|
1794
1444
|
* when the ledger moved. Read-only, from files on disk, nothing computed
|
|
@@ -1801,6 +1451,7 @@ function complianceOf(record) {
|
|
|
1801
1451
|
function handleApi(req, res, record) {
|
|
1802
1452
|
const route = (req.url ?? "").split("?")[0] ?? "";
|
|
1803
1453
|
if (!route.startsWith("/api/")) return false;
|
|
1454
|
+
apiVersion(res);
|
|
1804
1455
|
const json = (body, status = 200) => {
|
|
1805
1456
|
res.statusCode = status;
|
|
1806
1457
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
@@ -1815,19 +1466,56 @@ function handleApi(req, res, record) {
|
|
|
1815
1466
|
connection: "keep-alive"
|
|
1816
1467
|
});
|
|
1817
1468
|
res.write(": connected\n\n");
|
|
1818
|
-
|
|
1469
|
+
const ledgerPath = resolve(dir, files.ledger);
|
|
1470
|
+
const size = () => existsSync(ledgerPath) ? statSync(ledgerPath).size : 0;
|
|
1471
|
+
let offset = (() => {
|
|
1472
|
+
if (!existsSync(ledgerPath)) return 0;
|
|
1473
|
+
return readFileSync(ledgerPath).lastIndexOf(10) + 1;
|
|
1474
|
+
})();
|
|
1819
1475
|
const push = () => {
|
|
1820
|
-
const
|
|
1821
|
-
if (
|
|
1822
|
-
|
|
1823
|
-
|
|
1476
|
+
const end = size();
|
|
1477
|
+
if (end < offset) {
|
|
1478
|
+
offset = 0;
|
|
1479
|
+
res.write("event: reset\ndata: {}\n\n");
|
|
1480
|
+
}
|
|
1481
|
+
const fresh = [];
|
|
1482
|
+
if (end > offset) {
|
|
1483
|
+
const bytes = Buffer.alloc(end - offset);
|
|
1484
|
+
const fd = openSync(ledgerPath, "r");
|
|
1485
|
+
try {
|
|
1486
|
+
readSync(fd, bytes, 0, bytes.length, offset);
|
|
1487
|
+
} finally {
|
|
1488
|
+
closeSync(fd);
|
|
1489
|
+
}
|
|
1490
|
+
let at = 0;
|
|
1491
|
+
for (;;) {
|
|
1492
|
+
const nl = bytes.indexOf(10, at);
|
|
1493
|
+
if (nl === -1) break;
|
|
1494
|
+
const l = bytes.subarray(at, nl).toString("utf8");
|
|
1495
|
+
if (l.trim() !== "") try {
|
|
1496
|
+
fresh.push(JSON.parse(l));
|
|
1497
|
+
} catch {
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
at = nl + 1;
|
|
1501
|
+
}
|
|
1502
|
+
offset += at;
|
|
1503
|
+
}
|
|
1824
1504
|
if (fresh.length > 0) res.write(`data: ${JSON.stringify(fresh)}\n\n`);
|
|
1825
1505
|
else res.write(": keepalive\n\n");
|
|
1826
1506
|
};
|
|
1827
|
-
|
|
1507
|
+
let soon;
|
|
1508
|
+
const schedule = () => {
|
|
1509
|
+
soon ??= setTimeout(() => {
|
|
1510
|
+
soon = void 0;
|
|
1511
|
+
push();
|
|
1512
|
+
}, 250);
|
|
1513
|
+
};
|
|
1514
|
+
const watcher = existsSync(dir) ? watch(dir, { persistent: false }, schedule) : void 0;
|
|
1828
1515
|
const keep = setInterval(push, 2e3);
|
|
1829
1516
|
res.on("close", () => {
|
|
1830
1517
|
clearInterval(keep);
|
|
1518
|
+
if (soon !== void 0) clearTimeout(soon);
|
|
1831
1519
|
watcher?.close();
|
|
1832
1520
|
});
|
|
1833
1521
|
return true;
|
|
@@ -1851,11 +1539,32 @@ function handleApi(req, res, record) {
|
|
|
1851
1539
|
json(readRedteam(dir, files));
|
|
1852
1540
|
return true;
|
|
1853
1541
|
}
|
|
1542
|
+
if (route === "/api/status") {
|
|
1543
|
+
json({
|
|
1544
|
+
dir,
|
|
1545
|
+
files: Object.fromEntries(Object.entries(files).map(([name, file]) => {
|
|
1546
|
+
const path = resolve(dir, file);
|
|
1547
|
+
if (!existsSync(path)) return [name, null];
|
|
1548
|
+
const stat$1 = statSync(path);
|
|
1549
|
+
return [name, {
|
|
1550
|
+
size: stat$1.size,
|
|
1551
|
+
mtime: stat$1.mtimeMs
|
|
1552
|
+
}];
|
|
1553
|
+
}))
|
|
1554
|
+
});
|
|
1555
|
+
return true;
|
|
1556
|
+
}
|
|
1854
1557
|
const snapshot = readRecord(dir, files);
|
|
1855
1558
|
switch (route) {
|
|
1856
|
-
case "/api/record":
|
|
1857
|
-
|
|
1559
|
+
case "/api/record": {
|
|
1560
|
+
const over = snapshot.ledger.length - RECORD_WINDOW;
|
|
1561
|
+
json(over > 0 ? {
|
|
1562
|
+
...snapshot,
|
|
1563
|
+
ledger: snapshot.ledger.slice(over),
|
|
1564
|
+
trimmed: over
|
|
1565
|
+
} : snapshot);
|
|
1858
1566
|
break;
|
|
1567
|
+
}
|
|
1859
1568
|
case "/api/ledger":
|
|
1860
1569
|
json(snapshot.ledger);
|
|
1861
1570
|
break;
|
|
@@ -1871,20 +1580,6 @@ function handleApi(req, res, record) {
|
|
|
1871
1580
|
case "/api/pack":
|
|
1872
1581
|
json(snapshot.pack);
|
|
1873
1582
|
break;
|
|
1874
|
-
case "/api/status":
|
|
1875
|
-
json({
|
|
1876
|
-
dir,
|
|
1877
|
-
files: Object.fromEntries(Object.entries(files).map(([name, file]) => {
|
|
1878
|
-
const path = resolve(dir, file);
|
|
1879
|
-
if (!existsSync(path)) return [name, null];
|
|
1880
|
-
const stat$1 = statSync(path);
|
|
1881
|
-
return [name, {
|
|
1882
|
-
size: stat$1.size,
|
|
1883
|
-
mtime: stat$1.mtimeMs
|
|
1884
|
-
}];
|
|
1885
|
-
}))
|
|
1886
|
-
});
|
|
1887
|
-
break;
|
|
1888
1583
|
default: json({ error: "not found" }, 404);
|
|
1889
1584
|
}
|
|
1890
1585
|
return true;
|
|
@@ -1901,6 +1596,7 @@ function serveConsole(root, port, record) {
|
|
|
1901
1596
|
res.end();
|
|
1902
1597
|
return;
|
|
1903
1598
|
}
|
|
1599
|
+
if ((req.url ?? "").startsWith("/api/")) apiVersion(res);
|
|
1904
1600
|
if (record !== void 0 && handleApi(req, res, record)) return;
|
|
1905
1601
|
const target = safePath(root, req.url ?? "/");
|
|
1906
1602
|
if (target === void 0) {
|
|
@@ -2052,6 +1748,21 @@ const USAGE = `⌜▪⌟ prae — operator policy for agent harnesses
|
|
|
2052
1748
|
--pack-dir adds the workspace's own packs (e.g. .prae/packs) beside
|
|
2053
1749
|
that pack, loaded and watched as a set; a set that would not load is
|
|
2054
1750
|
not mounted (see \`prae check <dir>\`).
|
|
1751
|
+
|
|
1752
|
+
prae init claude-code (--settings <file> | --scope user|project)
|
|
1753
|
+
[--shadow | --enforce] [--pack <file>] [--ledger <file>]
|
|
1754
|
+
[--dir <dir>] [--apply] [--remove]
|
|
1755
|
+
Register PRAE as a Claude Code PreToolUse hook: every tool call,
|
|
1756
|
+
built-in or MCP, judged by the pack and sealed in a signed ledger
|
|
1757
|
+
(default ~/.prae/claude-code/). Shadow (the default) records and
|
|
1758
|
+
never blocks; --enforce returns deny/ask to Claude Code. Prints the
|
|
1759
|
+
settings diff; writes only with --apply. --remove takes out PRAE's
|
|
1760
|
+
own entry and nothing else. See docs/pilot-claude-code.md.
|
|
1761
|
+
|
|
1762
|
+
prae hook claude-code [--shadow | --enforce] --pack <file> --ledger <file>
|
|
1763
|
+
[--key <private.pem>] [--settings <file>]
|
|
1764
|
+
The command that settings entry runs: reads one PreToolUse payload on
|
|
1765
|
+
stdin and answers through the hook's output contract.
|
|
2055
1766
|
`;
|
|
2056
1767
|
function fail(message) {
|
|
2057
1768
|
console.error(`prae: ${message}`);
|
|
@@ -2111,7 +1822,7 @@ async function check(path, argv = []) {
|
|
|
2111
1822
|
* from the findings, so there is nothing here to be careful about printing.
|
|
2112
1823
|
*/
|
|
2113
1824
|
async function scanFile(path, packPath, json) {
|
|
2114
|
-
const { scan } = await import("./src-
|
|
1825
|
+
const { scan } = await import("./src-DMoYsdfo.js");
|
|
2115
1826
|
let content;
|
|
2116
1827
|
try {
|
|
2117
1828
|
content = await readFile(path, "utf8");
|
|
@@ -2151,13 +1862,13 @@ async function scanFile(path, packPath, json) {
|
|
|
2151
1862
|
* not come up inspecting nothing.
|
|
2152
1863
|
*/
|
|
2153
1864
|
async function mcp(root, packPath, ledgerPath) {
|
|
2154
|
-
const { serveMcp } = await import("./mcp-
|
|
1865
|
+
const { serveMcp } = await import("./mcp-D521EoP6.js");
|
|
2155
1866
|
const { resolve: resolve$1 } = await import("node:path");
|
|
2156
1867
|
const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
|
|
2157
1868
|
const chosen = packPath ?? bundled;
|
|
2158
1869
|
if (chosen === void 0) return fail("mcp needs --pack <file>; no bundled secret-catcher found");
|
|
2159
1870
|
const pack = await loadPack(chosen);
|
|
2160
|
-
const { Ledger: Ledger$1 } = await import("./src-
|
|
1871
|
+
const { Ledger: Ledger$1 } = await import("./src-wG36kT6K.js");
|
|
2161
1872
|
const ledger = ledgerPath === void 0 ? void 0 : Ledger$1.open(ledgerPath);
|
|
2162
1873
|
console.error(`prae mcp: serving ${resolve$1(root)} under ${pack.pack} ${pack.version}` + (ledgerPath === void 0 ? " (no ledger)" : ` · ledger ${ledgerPath}`));
|
|
2163
1874
|
await serveMcp({
|
|
@@ -2209,7 +1920,7 @@ async function explain(path, tool, pairs) {
|
|
|
2209
1920
|
* a key that arrived beside it is checked against nothing.
|
|
2210
1921
|
*/
|
|
2211
1922
|
async function keygen(dir) {
|
|
2212
|
-
const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-
|
|
1923
|
+
const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
|
|
2213
1924
|
const { writeFileSync: writeFileSync$1 } = await import("node:fs");
|
|
2214
1925
|
const keys = generateKeypair();
|
|
2215
1926
|
const priv = join(dir, "prae-signing.pem");
|
|
@@ -2232,7 +1943,7 @@ async function keygen(dir) {
|
|
|
2232
1943
|
async function keygenSealed(role, dir) {
|
|
2233
1944
|
if (role !== "operator" && role !== "policy" && role !== "ledger") fail("--role is operator, policy or ledger");
|
|
2234
1945
|
const stem = role === "ledger" ? "prae-signing" : role;
|
|
2235
|
-
const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-
|
|
1946
|
+
const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
|
|
2236
1947
|
const { writeFileSync: writeFileSync$1 } = await import("node:fs");
|
|
2237
1948
|
const keys = sealedKeypair(process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: "));
|
|
2238
1949
|
const pub = join(dir, `${stem}.pub.pem`);
|
|
@@ -2245,11 +1956,11 @@ async function keygenSealed(role, dir) {
|
|
|
2245
1956
|
}
|
|
2246
1957
|
/** A detached signature over a pack's bytes (PRA-107). */
|
|
2247
1958
|
async function signPack(packPath, keyPath) {
|
|
2248
|
-
const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-
|
|
1959
|
+
const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
|
|
2249
1960
|
const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
|
|
2250
1961
|
const bytes = readFileSync(packPath);
|
|
2251
|
-
const parsed = parsePack(bytes.toString("utf8"));
|
|
2252
|
-
if (!parsed.ok) fail(`${packPath} does not parse; nothing signed`);
|
|
1962
|
+
const parsed$1 = parsePack(bytes.toString("utf8"));
|
|
1963
|
+
if (!parsed$1.ok) fail(`${packPath} does not parse; nothing signed`);
|
|
2253
1964
|
const passphrase = process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: ");
|
|
2254
1965
|
let key;
|
|
2255
1966
|
try {
|
|
@@ -2262,7 +1973,7 @@ async function signPack(packPath, keyPath) {
|
|
|
2262
1973
|
type: "spki",
|
|
2263
1974
|
format: "pem"
|
|
2264
1975
|
});
|
|
2265
|
-
console.log(`signed ${packPath}.sig (${parsed.pack.pack} ${parsed.pack.version})`);
|
|
1976
|
+
console.log(`signed ${packPath}.sig (${parsed$1.pack.pack} ${parsed$1.pack.version})`);
|
|
2266
1977
|
console.log(`policy key ${fingerprint$1(pub.toString())}`);
|
|
2267
1978
|
}
|
|
2268
1979
|
/** Read a line from the terminal without echoing it. */
|
|
@@ -2290,7 +2001,7 @@ function askHidden(prompt) {
|
|
|
2290
2001
|
});
|
|
2291
2002
|
}
|
|
2292
2003
|
async function receipt(path, seq, keyPath, out) {
|
|
2293
|
-
const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-
|
|
2004
|
+
const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
|
|
2294
2005
|
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
2295
2006
|
const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
|
|
2296
2007
|
const privateKey = readFileSync$1(keyPath, "utf8");
|
|
@@ -2320,14 +2031,14 @@ async function receipt(path, seq, keyPath, out) {
|
|
|
2320
2031
|
* file and sets the exit code, so scripts can branch on it.
|
|
2321
2032
|
*/
|
|
2322
2033
|
async function verifyReceiptCmd(path) {
|
|
2323
|
-
const { checkReceipt } = await import("./receipt-check-
|
|
2324
|
-
let parsed;
|
|
2034
|
+
const { checkReceipt } = await import("./receipt-check-CUuEjGHL.js");
|
|
2035
|
+
let parsed$1;
|
|
2325
2036
|
try {
|
|
2326
|
-
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
2037
|
+
parsed$1 = JSON.parse(await readFile(path, "utf8"));
|
|
2327
2038
|
} catch {
|
|
2328
2039
|
return fail(`cannot read a receipt from ${path}`);
|
|
2329
2040
|
}
|
|
2330
|
-
const result = checkReceipt(parsed);
|
|
2041
|
+
const result = checkReceipt(parsed$1);
|
|
2331
2042
|
for (const line of result.lines) if (result.ok) console.log(line);
|
|
2332
2043
|
else console.error(line);
|
|
2333
2044
|
if (!result.ok) process.exit(1);
|
|
@@ -2345,14 +2056,14 @@ async function test(suitePath, packOverride, asJson) {
|
|
|
2345
2056
|
} catch {
|
|
2346
2057
|
return fail(`cannot read ${suitePath}`);
|
|
2347
2058
|
}
|
|
2348
|
-
let parsed;
|
|
2059
|
+
let parsed$1;
|
|
2349
2060
|
try {
|
|
2350
|
-
parsed = JSON.parse(raw);
|
|
2061
|
+
parsed$1 = JSON.parse(raw);
|
|
2351
2062
|
} catch (error) {
|
|
2352
2063
|
return fail(`${suitePath} is not valid JSON: ${error instanceof Error ? error.message : error}`);
|
|
2353
2064
|
}
|
|
2354
|
-
const { parseSuite, renderSuite, runSuite } = await import("./test-run-
|
|
2355
|
-
const suite = parseSuite(parsed);
|
|
2065
|
+
const { parseSuite, renderSuite, runSuite } = await import("./test-run-C8dHjIE2.js");
|
|
2066
|
+
const suite = parseSuite(parsed$1);
|
|
2356
2067
|
if (!suite.ok) {
|
|
2357
2068
|
console.error(`prae: ${suitePath} is not a valid test suite`);
|
|
2358
2069
|
for (const message of suite.errors) console.error(` ${message}`);
|
|
@@ -2362,7 +2073,7 @@ async function test(suitePath, packOverride, asJson) {
|
|
|
2362
2073
|
if (chosen === void 0) return fail("test needs a pack: set `pack` in the suite or pass --pack");
|
|
2363
2074
|
const pack = await loadPack(chosen);
|
|
2364
2075
|
const { capabilityForTool } = await import("capmark");
|
|
2365
|
-
const { scan } = await import("./src-
|
|
2076
|
+
const { scan } = await import("./src-DMoYsdfo.js");
|
|
2366
2077
|
const inspect = pack.rules.some((rule) => rule.when.secrets !== void 0) ? (path) => {
|
|
2367
2078
|
try {
|
|
2368
2079
|
return scan(readFileSync(path, "utf8"));
|
|
@@ -2391,14 +2102,14 @@ async function verify$1(path, keyPath, checkpointPath) {
|
|
|
2391
2102
|
const last = entries.at(-1);
|
|
2392
2103
|
console.log(`${result.entries} entries · chain intact${last === void 0 ? "" : ` · seal ${seal(last)}`}`);
|
|
2393
2104
|
if (checkpointPath !== void 0) {
|
|
2394
|
-
const { checkCheckpoint } = await import("./checkpoint-
|
|
2105
|
+
const { checkCheckpoint } = await import("./checkpoint-BqJ8xwky.js");
|
|
2395
2106
|
const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
|
|
2396
2107
|
const verdict = checkCheckpoint(entries, JSON.parse(readFileSync(checkpointPath, "utf8")), publicKey);
|
|
2397
2108
|
for (const line of verdict.lines) (verdict.ok ? console.log : console.error)(`checkpoint: ${line}`);
|
|
2398
2109
|
if (!verdict.ok) process.exitCode = 1;
|
|
2399
2110
|
}
|
|
2400
2111
|
if (keyPath !== void 0) {
|
|
2401
|
-
const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-
|
|
2112
|
+
const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
|
|
2402
2113
|
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
2403
2114
|
const publicKey = readFileSync$1(keyPath, "utf8");
|
|
2404
2115
|
let signed = 0;
|
|
@@ -2476,7 +2187,7 @@ async function report(path, out, packPath) {
|
|
|
2476
2187
|
* that step is a proposal a human approves, and it starts from this list.
|
|
2477
2188
|
*/
|
|
2478
2189
|
async function instructions(from, asJson) {
|
|
2479
|
-
const { ingest } = await import("./src-
|
|
2190
|
+
const { ingest } = await import("./src-CDYyZyeU.js");
|
|
2480
2191
|
const result = ingest(from);
|
|
2481
2192
|
if (asJson) {
|
|
2482
2193
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -2512,7 +2223,7 @@ async function instructions(from, asJson) {
|
|
|
2512
2223
|
* on purpose rather than something that happened to their policy.
|
|
2513
2224
|
*/
|
|
2514
2225
|
async function propose(from, options) {
|
|
2515
|
-
const [{ ingest }, { extract }] = await Promise.all([import("./src-
|
|
2226
|
+
const [{ ingest }, { extract }] = await Promise.all([import("./src-CDYyZyeU.js"), import("./src-BlmsTGdY.js")]);
|
|
2516
2227
|
const read = ingest(from);
|
|
2517
2228
|
if (read.files.length === 0) {
|
|
2518
2229
|
console.log(`No instruction files found at or above ${from}.`);
|
|
@@ -2572,9 +2283,9 @@ async function consoleOut(path, argv) {
|
|
|
2572
2283
|
const instructionsDir = flag(argv, "instructions");
|
|
2573
2284
|
if (instructionsDir !== void 0) {
|
|
2574
2285
|
const [{ ingest }, { extract }, { renderInstructions }] = await Promise.all([
|
|
2575
|
-
import("./src-
|
|
2576
|
-
import("./src-
|
|
2577
|
-
import("./instructions-
|
|
2286
|
+
import("./src-CDYyZyeU.js"),
|
|
2287
|
+
import("./src-BlmsTGdY.js"),
|
|
2288
|
+
import("./instructions-DihPiiC2.js")
|
|
2578
2289
|
]);
|
|
2579
2290
|
const read = ingest(instructionsDir);
|
|
2580
2291
|
instructionsView = renderInstructions(data, read, extract(read.statements));
|
|
@@ -2615,9 +2326,12 @@ async function consoleOut(path, argv) {
|
|
|
2615
2326
|
console.log(`record at ${url}api/record · events at ${url}api/events`);
|
|
2616
2327
|
console.log("loopback only, read-only. ctrl-c to stop.");
|
|
2617
2328
|
}
|
|
2329
|
+
/** The bundled baseline: beside the built code, or in examples/ in source. */
|
|
2330
|
+
function bundledBaseline() {
|
|
2331
|
+
return [join(import.meta.dirname, "prae-baseline.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "prae-baseline.prae.json")].find((candidate) => existsSync(candidate));
|
|
2332
|
+
}
|
|
2618
2333
|
async function init(argv) {
|
|
2619
|
-
const
|
|
2620
|
-
const packPath = flag(argv, "pack") ?? bundledBaseline;
|
|
2334
|
+
const packPath = flag(argv, "pack") ?? bundledBaseline();
|
|
2621
2335
|
if (packPath === void 0) fail("init needs --pack <file>; no bundled baseline found");
|
|
2622
2336
|
if (flag(argv, "pack") === void 0) {
|
|
2623
2337
|
console.log("No --pack given; mounting the built-in baseline (8 rules).");
|
|
@@ -2704,7 +2418,7 @@ async function chainMatrix(path, argv) {
|
|
|
2704
2418
|
const catalogue = guardiansPath === void 0 ? [] : JSON.parse(readFileSync(guardiansPath, "utf8")).guardians;
|
|
2705
2419
|
const guardianOf = (rule) => rule === void 0 ? void 0 : catalogue.find((g) => g.rules.includes(rule) || (g.armed ?? []).includes(rule))?.name;
|
|
2706
2420
|
const keyPath = flag(argv, "key");
|
|
2707
|
-
const { verifySignature: verifySignature$1 } = await import("./src-
|
|
2421
|
+
const { verifySignature: verifySignature$1 } = await import("./src-wG36kT6K.js");
|
|
2708
2422
|
const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
|
|
2709
2423
|
const matrix = integrityMatrix(entries, pack, guardianOf).map((m) => {
|
|
2710
2424
|
const entry = entries.find((e) => e.seq === m.seq);
|
|
@@ -2735,7 +2449,7 @@ async function chainMatrix(path, argv) {
|
|
|
2735
2449
|
if (!chainOk || sigFailed) process.exitCode = 1;
|
|
2736
2450
|
}
|
|
2737
2451
|
async function checkpoint(path, keyPath, out) {
|
|
2738
|
-
const { makeCheckpoint } = await import("./checkpoint-
|
|
2452
|
+
const { makeCheckpoint } = await import("./checkpoint-BqJ8xwky.js");
|
|
2739
2453
|
let ledger;
|
|
2740
2454
|
try {
|
|
2741
2455
|
ledger = Ledger.open(path);
|
|
@@ -2756,9 +2470,9 @@ async function checkpoint(path, keyPath, out) {
|
|
|
2756
2470
|
}
|
|
2757
2471
|
}
|
|
2758
2472
|
async function exportLedger(path, options) {
|
|
2759
|
-
const { toOcsf, ndjson, sendToHec } = await import("./ocsf-
|
|
2760
|
-
const { createRequire } = await import("node:module");
|
|
2761
|
-
const { version } = createRequire(import.meta.url)("../package.json");
|
|
2473
|
+
const { toOcsf, ndjson, sendToHec } = await import("./ocsf-DF9cMiQJ.js");
|
|
2474
|
+
const { createRequire: createRequire$1 } = await import("node:module");
|
|
2475
|
+
const { version } = createRequire$1(import.meta.url)("../package.json");
|
|
2762
2476
|
let ledger;
|
|
2763
2477
|
try {
|
|
2764
2478
|
ledger = Ledger.open(path);
|
|
@@ -2885,7 +2599,26 @@ async function main(argv) {
|
|
|
2885
2599
|
...flag(rest, "out") === void 0 ? {} : { out: flag(rest, "out") }
|
|
2886
2600
|
});
|
|
2887
2601
|
}
|
|
2888
|
-
case "init":
|
|
2602
|
+
case "init":
|
|
2603
|
+
if (rest[0] === "claude-code") {
|
|
2604
|
+
const { initClaudeCode, stableNode } = await import("./init-DRd-IRR6.js");
|
|
2605
|
+
try {
|
|
2606
|
+
return initClaudeCode(rest.slice(1), {
|
|
2607
|
+
cwd: process.cwd(),
|
|
2608
|
+
node: stableNode(process.execPath, process.env.PATH),
|
|
2609
|
+
bin: resolve(process.argv[1] ?? ""),
|
|
2610
|
+
baseline: bundledBaseline()
|
|
2611
|
+
});
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
return init(rest);
|
|
2617
|
+
case "hook": {
|
|
2618
|
+
if (rest[0] !== "claude-code") fail("hook takes claude-code");
|
|
2619
|
+
const { runHook } = await import("./hook-Gll2spTP.js");
|
|
2620
|
+
return runHook(rest.slice(1));
|
|
2621
|
+
}
|
|
2889
2622
|
case "--help":
|
|
2890
2623
|
case "-h":
|
|
2891
2624
|
case void 0:
|