prae-cli 0.9.2 → 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 +2 -6
- package/lib/{checkpoint-C_q_Rpa5.js → checkpoint-BLOGdWFu.js} +1 -1
- package/lib/cli-BlWTUnGJ.js +6 -0
- package/lib/{cli-B_B6-bUZ.js → cli-C_6dosaw.js} +629 -534
- package/lib/docs/pilot-claude-code.md +192 -0
- package/lib/hook-DxLgNtc4.js +693 -0
- package/lib/index.js +4 -4
- package/lib/init-BPlBJvA-.js +272 -0
- package/lib/{instructions-ya0LVnZy.js → instructions-fCJ4j7ce.js} +2 -1
- package/lib/{mcp-BCXbpqu4.js → mcp-DdEDvTvR.js} +1 -1
- package/lib/playground.js +717 -109
- package/lib/prae-baseline.prae.json +144 -1
- package/lib/{receipt-check-DQK_gVPc.js → receipt-check-2F4zLEW5.js} +1 -1
- package/lib/src-2iJy5MZc.js +3 -0
- package/lib/src-CZYvvEn5.js +1691 -0
- package/lib/{src-LA7NdLKb.js → src-CuiWC80o.js} +104 -25
- package/lib/{test-run-CCDgJ0Dp.js → test-run-BGsK-inZ.js} +1 -1
- package/lib/views-C7DD-Oif.js +1396 -0
- package/package.json +6 -6
- package/lib/evaluate-BqLxhYiP.js +0 -499
- package/lib/src-7X6dO7gC.js +0 -3
- package/lib/views-D4UEfbzt.js +0 -1505
- /package/lib/{ocsf-DhVGbBUw.js → ocsf-BwWyM7hc.js} +0 -0
- /package/lib/{src-CBGZVvt7.js → src-BoBCmiwj.js} +0 -0
- /package/lib/{src-d4c_n6wd.js → src-XukJfuml.js} +0 -0
|
@@ -1,456 +1,16 @@
|
|
|
1
|
-
import { n as hostOf, r as
|
|
2
|
-
import { a as
|
|
3
|
-
import { c as
|
|
4
|
-
import {
|
|
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";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
|
|
5
6
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
7
|
import { basename, dirname, extname, join, normalize, resolve, sep } from "node:path";
|
|
7
8
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { parse } from "yaml";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
+
import { parse } from "yaml";
|
|
10
11
|
import { composePatchFile, preflightOps, revisionOf, takeSnapshot } from "dsh-overlay-check";
|
|
11
12
|
import { createServer } from "node:http";
|
|
12
13
|
|
|
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
14
|
//#region ../dsh/src/chain.ts
|
|
455
15
|
const STAGES = [
|
|
456
16
|
{
|
|
@@ -707,14 +267,14 @@ function readPackFile(file, policyKey, origin = "workspace") {
|
|
|
707
267
|
reason: `invalid YAML: ${e instanceof Error ? e.message : String(e)}`
|
|
708
268
|
};
|
|
709
269
|
}
|
|
710
|
-
const parsed = parsePack(json);
|
|
711
|
-
if (!parsed.ok) return {
|
|
270
|
+
const parsed$1 = parsePack(json);
|
|
271
|
+
if (!parsed$1.ok) return {
|
|
712
272
|
file,
|
|
713
|
-
reason: parsed.errors.map((e) => `${e.path || "<root>"}: ${e.message}`).join("; ")
|
|
273
|
+
reason: parsed$1.errors.map((e) => `${e.path || "<root>"}: ${e.message}`).join("; ")
|
|
714
274
|
};
|
|
715
275
|
return {
|
|
716
276
|
file,
|
|
717
|
-
pack: parsed.pack,
|
|
277
|
+
pack: parsed$1.pack,
|
|
718
278
|
origin,
|
|
719
279
|
sha256,
|
|
720
280
|
...policyKey === void 0 ? {} : { signedBy: fingerprint(policyKey) }
|
|
@@ -809,6 +369,7 @@ function merge(packs, rejected) {
|
|
|
809
369
|
};
|
|
810
370
|
const baseline = packs.find((p) => p.pack.baseline !== void 0)?.pack.baseline;
|
|
811
371
|
const only = packs.length === 1 ? packs[0] : void 0;
|
|
372
|
+
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
373
|
return {
|
|
813
374
|
packs,
|
|
814
375
|
rejected,
|
|
@@ -818,6 +379,7 @@ function merge(packs, rejected) {
|
|
|
818
379
|
pack: only?.pack.pack ?? packs.map((p) => p.pack.pack).join("+"),
|
|
819
380
|
version: only?.pack.version ?? createHash("sha256").update(packs.map((p) => p.sha256).join("")).digest("hex").slice(0, 12),
|
|
820
381
|
...baseline === void 0 ? {} : { baseline },
|
|
382
|
+
...expires === void 0 ? {} : { expires_at: expires },
|
|
821
383
|
rules: packs.flatMap((p) => p.pack.rules)
|
|
822
384
|
}
|
|
823
385
|
};
|
|
@@ -843,7 +405,7 @@ function row(entry, pack) {
|
|
|
843
405
|
<div class="call">${esc(call)}</div>
|
|
844
406
|
<div class="actor">${esc(actor)}</div>
|
|
845
407
|
<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>
|
|
408
|
+
<div class="verdict v-${esc(entry.verdict)}">${esc(VERDICT_LABEL$1[entry.verdict] ?? entry.verdict)}${entry.shadow === true ? " · SHADOW" : ""}</div>
|
|
847
409
|
<div class="seal">${esc(seal(entry))}</div>
|
|
848
410
|
</div>`;
|
|
849
411
|
}
|
|
@@ -851,12 +413,14 @@ function renderLedgerView(entries, verification, options) {
|
|
|
851
413
|
const counts = {};
|
|
852
414
|
const packs = /* @__PURE__ */ new Set();
|
|
853
415
|
const sessions = /* @__PURE__ */ new Set();
|
|
416
|
+
let shadowed = 0;
|
|
854
417
|
for (const entry of entries) {
|
|
855
|
-
|
|
418
|
+
if (entry.shadow === true) shadowed += 1;
|
|
419
|
+
else counts[entry.verdict] = (counts[entry.verdict] ?? 0) + 1;
|
|
856
420
|
packs.add(`${entry.pack.name} ${entry.pack.version}`);
|
|
857
421
|
if (entry.session !== void 0) sessions.add(entry.session);
|
|
858
422
|
}
|
|
859
|
-
const held = entries.filter((e) => e.verdict === "ask");
|
|
423
|
+
const held = entries.filter((e) => e.verdict === "ask" && e.shadow !== true);
|
|
860
424
|
const pack = [...packs][0] ?? "—";
|
|
861
425
|
const now = Date.parse(options.generatedAt);
|
|
862
426
|
const chainOk = verification.ok;
|
|
@@ -866,6 +430,7 @@ function renderLedgerView(entries, verification, options) {
|
|
|
866
430
|
<div class="meta">${esc([
|
|
867
431
|
`${sessions.size || 1} session${sessions.size === 1 ? "" : "s"}`,
|
|
868
432
|
`${entries.length.toLocaleString("en-US")} decisions`,
|
|
433
|
+
...shadowed === 0 ? [] : [`${shadowed} in shadow mode (recorded, not enforced)`],
|
|
869
434
|
options.retention === void 0 ? "retention local" : `retention ${options.retention}`
|
|
870
435
|
].join(" · "))}</div>
|
|
871
436
|
</div>
|
|
@@ -1201,16 +766,60 @@ function jsonOrNull(path) {
|
|
|
1201
766
|
return null;
|
|
1202
767
|
}
|
|
1203
768
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
const
|
|
1207
|
-
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
769
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
770
|
+
function parseLines(text, into) {
|
|
771
|
+
for (const line of text.split("\n")) {
|
|
1208
772
|
if (line.trim() === "") continue;
|
|
1209
773
|
try {
|
|
1210
|
-
|
|
774
|
+
into.push(JSON.parse(line));
|
|
1211
775
|
} catch {}
|
|
1212
776
|
}
|
|
1213
|
-
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* The ledger's rows. A ledger is append-only, so the parse is kept and only
|
|
780
|
+
* the bytes appended since are read: a long demo grows it to tens of
|
|
781
|
+
* thousands of rows, and every endpoint the console polls reads it — parsed
|
|
782
|
+
* whole each time, that kept the server too busy to answer anyone. A file
|
|
783
|
+
* that shrank or was replaced is read again from the start. A half-written
|
|
784
|
+
* last line is left unread until its newline arrives.
|
|
785
|
+
*/
|
|
786
|
+
function readLedger(path) {
|
|
787
|
+
if (!existsSync(path)) {
|
|
788
|
+
parsed.delete(path);
|
|
789
|
+
return [];
|
|
790
|
+
}
|
|
791
|
+
const stat$1 = statSync(path);
|
|
792
|
+
let entry = parsed.get(path);
|
|
793
|
+
if (entry === void 0 || entry.ino !== stat$1.ino || stat$1.size < entry.size) {
|
|
794
|
+
entry = {
|
|
795
|
+
ino: stat$1.ino,
|
|
796
|
+
size: 0,
|
|
797
|
+
mtimeMs: 0,
|
|
798
|
+
offset: 0,
|
|
799
|
+
rows: []
|
|
800
|
+
};
|
|
801
|
+
parsed.set(path, entry);
|
|
802
|
+
}
|
|
803
|
+
if (stat$1.size !== entry.size || stat$1.mtimeMs !== entry.mtimeMs) {
|
|
804
|
+
const length = stat$1.size - entry.offset;
|
|
805
|
+
if (length > 0) {
|
|
806
|
+
const buf = Buffer.alloc(length);
|
|
807
|
+
const fd = openSync(path, "r");
|
|
808
|
+
try {
|
|
809
|
+
readSync(fd, buf, 0, length, entry.offset);
|
|
810
|
+
} finally {
|
|
811
|
+
closeSync(fd);
|
|
812
|
+
}
|
|
813
|
+
const end = buf.lastIndexOf(10);
|
|
814
|
+
if (end >= 0) {
|
|
815
|
+
parseLines(buf.subarray(0, end).toString("utf8"), entry.rows);
|
|
816
|
+
entry.offset += end + 1;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
entry.size = stat$1.size;
|
|
820
|
+
entry.mtimeMs = stat$1.mtimeMs;
|
|
821
|
+
}
|
|
822
|
+
return entry.rows.slice();
|
|
1214
823
|
}
|
|
1215
824
|
/** The defaults the demo writes; a caller with other names overrides them. */
|
|
1216
825
|
const DEFAULT_FILES = {
|
|
@@ -1229,7 +838,8 @@ function readRecord(dir, files) {
|
|
|
1229
838
|
discovery: jsonOrNull(resolve(dir, files.discovery)),
|
|
1230
839
|
report: jsonOrNull(resolve(dir, files.report)),
|
|
1231
840
|
guardians: jsonOrNull(resolve(dir, files.guardians)),
|
|
1232
|
-
pack: jsonOrNull(resolve(dir, files.pack))
|
|
841
|
+
pack: jsonOrNull(resolve(dir, files.pack)),
|
|
842
|
+
crownJewel: jsonOrNull(resolve(dir, "crown-jewel.json"))
|
|
1233
843
|
};
|
|
1234
844
|
}
|
|
1235
845
|
/** The red-team campaign and its runs, as the runner wrote them; empty when none. */
|
|
@@ -1238,17 +848,342 @@ function readRedteam(dir, files) {
|
|
|
1238
848
|
if (!existsSync(root)) return {
|
|
1239
849
|
campaign: null,
|
|
1240
850
|
runs: [],
|
|
1241
|
-
steering: []
|
|
851
|
+
steering: [],
|
|
852
|
+
history: []
|
|
1242
853
|
};
|
|
1243
854
|
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
855
|
const steeringDir = resolve(root, "steering");
|
|
1245
856
|
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) : [];
|
|
857
|
+
const historyDir = resolve(steeringDir, "history");
|
|
858
|
+
const history = existsSync(historyDir) ? readdirSync(historyDir).filter((n) => n.endsWith(".json")).sort().reverse().map((n) => jsonOrNull(resolve(historyDir, n))).filter((r) => r !== null) : [];
|
|
1246
859
|
return {
|
|
1247
860
|
campaign: jsonOrNull(resolve(root, "campaign.json")),
|
|
1248
861
|
runs,
|
|
1249
|
-
steering
|
|
862
|
+
steering,
|
|
863
|
+
history
|
|
864
|
+
};
|
|
865
|
+
}
|
|
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}`))
|
|
1250
1136
|
};
|
|
1251
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
|
+
}
|
|
1252
1187
|
|
|
1253
1188
|
//#endregion
|
|
1254
1189
|
//#region ../controls/src/scope.ts
|
|
@@ -1327,10 +1262,11 @@ function catalogOf(file) {
|
|
|
1327
1262
|
};
|
|
1328
1263
|
frameworks.set(framework.id, f);
|
|
1329
1264
|
}
|
|
1265
|
+
const name = control.name ?? FRAMEWORKS[framework.id]?.controls[id]?.name;
|
|
1330
1266
|
f.controls.push({
|
|
1331
1267
|
key,
|
|
1332
1268
|
id,
|
|
1333
|
-
...
|
|
1269
|
+
...name === void 0 ? {} : { name },
|
|
1334
1270
|
asks: control.asks
|
|
1335
1271
|
});
|
|
1336
1272
|
};
|
|
@@ -1424,6 +1360,11 @@ function inPeriod(at, period, now) {
|
|
|
1424
1360
|
if (period.kind === "days") return t >= now - period.days * 864e5;
|
|
1425
1361
|
return t >= Date.parse(period.from) && t < Date.parse(period.to) + 864e5;
|
|
1426
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
|
+
}
|
|
1427
1368
|
/**
|
|
1428
1369
|
* Assess the scoped controls against the record. The rows are narrowed to
|
|
1429
1370
|
* the scope's agents, sessions and period before any control reads them;
|
|
@@ -1433,7 +1374,7 @@ function inPeriod(at, period, now) {
|
|
|
1433
1374
|
function assess(data, scope, catalog, options) {
|
|
1434
1375
|
const now = options.now ?? Date.now();
|
|
1435
1376
|
const agentOf$1 = (e) => e.agent ?? options.agent;
|
|
1436
|
-
const admitted = data.entries
|
|
1377
|
+
const admitted = admit(data.entries, scope, options.agent, now);
|
|
1437
1378
|
const scoped = {
|
|
1438
1379
|
...data,
|
|
1439
1380
|
entries: admitted
|
|
@@ -1580,17 +1521,39 @@ function agentOf(dir) {
|
|
|
1580
1521
|
} catch {}
|
|
1581
1522
|
return "this agent";
|
|
1582
1523
|
}
|
|
1583
|
-
|
|
1524
|
+
/** What a record's compliance is assessed against: its catalog and scope. */
|
|
1525
|
+
function complianceContext(record) {
|
|
1584
1526
|
const { catalog, catalogError } = catalogFor(record.dir);
|
|
1585
1527
|
return {
|
|
1586
|
-
|
|
1528
|
+
catalog,
|
|
1529
|
+
...catalogError === void 0 ? {} : { catalogError },
|
|
1530
|
+
scope: scopeFor(record.dir, catalog),
|
|
1531
|
+
options: {
|
|
1587
1532
|
agent: agentOf(record.dir),
|
|
1588
1533
|
environment: basename(resolve(record.dir))
|
|
1589
|
-
}
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
function assessCompliance(record, data) {
|
|
1538
|
+
const { catalog, catalogError, scope, options } = complianceContext(record);
|
|
1539
|
+
return {
|
|
1540
|
+
...assess(data, scope, catalog, options),
|
|
1590
1541
|
saved: existsSync(resolve(record.dir, SCOPE_FILE)),
|
|
1591
1542
|
...catalogError === void 0 ? {} : { catalogError }
|
|
1592
1543
|
};
|
|
1593
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
|
+
}
|
|
1594
1557
|
|
|
1595
1558
|
//#endregion
|
|
1596
1559
|
//#region src/serve.ts
|
|
@@ -1647,6 +1610,23 @@ function verifyRecord(record, tamper, repair = false) {
|
|
|
1647
1610
|
repaired: false,
|
|
1648
1611
|
signatures: null
|
|
1649
1612
|
};
|
|
1613
|
+
if (tamper === void 0) {
|
|
1614
|
+
const keyPath$1 = resolve(dir, files.publicKey);
|
|
1615
|
+
const publicKey = existsSync(keyPath$1) ? readFileSync(keyPath$1, "utf8") : void 0;
|
|
1616
|
+
const v = verifyGrowing(path, publicKey);
|
|
1617
|
+
return {
|
|
1618
|
+
ok: v.ok,
|
|
1619
|
+
entries: v.entries,
|
|
1620
|
+
problems: [...v.problems],
|
|
1621
|
+
tampered: null,
|
|
1622
|
+
repaired: false,
|
|
1623
|
+
signatures: v.signatures === null || publicKey === void 0 ? null : {
|
|
1624
|
+
checked: v.signatures.checked,
|
|
1625
|
+
failed: [...v.signatures.failed],
|
|
1626
|
+
fingerprint: fingerprint(publicKey)
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1650
1630
|
let text = readFileSync(path, "utf8");
|
|
1651
1631
|
let tampered = null;
|
|
1652
1632
|
if (tamper !== void 0) {
|
|
@@ -1754,23 +1734,38 @@ function packsOf(record) {
|
|
|
1754
1734
|
function complianceOf(record) {
|
|
1755
1735
|
const { dir, files } = record;
|
|
1756
1736
|
const ledgerPath = resolve(dir, files.ledger);
|
|
1757
|
-
const
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
}
|
|
1737
|
+
const entries = readLedger(ledgerPath);
|
|
1738
|
+
const grown = existsSync(ledgerPath) ? verifyGrowing(ledgerPath) : void 0;
|
|
1739
|
+
const verification = grown === void 0 ? verifyEntries("") : {
|
|
1740
|
+
ok: grown.ok,
|
|
1741
|
+
entries: grown.entries,
|
|
1742
|
+
problems: grown.problems
|
|
1743
|
+
};
|
|
1764
1744
|
const { set } = packSetOf(record);
|
|
1765
|
-
const
|
|
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
|
+
};
|
|
1766
1752
|
const data = {
|
|
1767
1753
|
...pack === void 0 ? {} : { pack },
|
|
1768
|
-
entries,
|
|
1769
|
-
verification
|
|
1754
|
+
entries: evidenceRows(entries),
|
|
1755
|
+
verification,
|
|
1770
1756
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1771
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
|
+
}])));
|
|
1772
1766
|
return {
|
|
1773
|
-
assessment
|
|
1767
|
+
assessment,
|
|
1768
|
+
inspector: inspectorOf(record, data, assessment.scope, origins, deployments, refused),
|
|
1774
1769
|
controls: CONTROLS.map((control) => {
|
|
1775
1770
|
const found = Object.entries(FRAMEWORKS).find(([, f]) => control.id.startsWith(`${f.short} · `));
|
|
1776
1771
|
const code = found === void 0 ? void 0 : control.id.split(" · ")[1];
|
|
@@ -1789,6 +1784,23 @@ function complianceOf(record) {
|
|
|
1789
1784
|
};
|
|
1790
1785
|
}
|
|
1791
1786
|
/**
|
|
1787
|
+
* The API's version is prae-cli's: every `/api/*` response names it in
|
|
1788
|
+
* `X-PRAE-API-Version`, so a client can tell which contract answered. The
|
|
1789
|
+
* contract follows the package's semver — a breaking change to a route bumps
|
|
1790
|
+
* the major — and a route on its way out carries `Deprecation` and `Sunset`
|
|
1791
|
+
* (RFC 9745 / RFC 8594) for at least one minor release before it goes.
|
|
1792
|
+
*/
|
|
1793
|
+
/**
|
|
1794
|
+
* The most rows `/api/record` sends: the newest ones. The console keeps the
|
|
1795
|
+
* same window (apps/console/src/data/hooks.ts); `trimmed` counts the rest.
|
|
1796
|
+
*/
|
|
1797
|
+
const RECORD_WINDOW = 25e3;
|
|
1798
|
+
const API_VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
1799
|
+
/** Name the contract on an API response; call before the status is written. */
|
|
1800
|
+
function apiVersion(res) {
|
|
1801
|
+
res.setHeader("x-prae-api-version", API_VERSION);
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1792
1804
|
* The record over HTTP, for the Command Center (apps/console): the same
|
|
1793
1805
|
* files `prae console` renders, as JSON, plus one event stream that says
|
|
1794
1806
|
* when the ledger moved. Read-only, from files on disk, nothing computed
|
|
@@ -1801,6 +1813,7 @@ function complianceOf(record) {
|
|
|
1801
1813
|
function handleApi(req, res, record) {
|
|
1802
1814
|
const route = (req.url ?? "").split("?")[0] ?? "";
|
|
1803
1815
|
if (!route.startsWith("/api/")) return false;
|
|
1816
|
+
apiVersion(res);
|
|
1804
1817
|
const json = (body, status = 200) => {
|
|
1805
1818
|
res.statusCode = status;
|
|
1806
1819
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
@@ -1815,19 +1828,56 @@ function handleApi(req, res, record) {
|
|
|
1815
1828
|
connection: "keep-alive"
|
|
1816
1829
|
});
|
|
1817
1830
|
res.write(": connected\n\n");
|
|
1818
|
-
|
|
1831
|
+
const ledgerPath = resolve(dir, files.ledger);
|
|
1832
|
+
const size = () => existsSync(ledgerPath) ? statSync(ledgerPath).size : 0;
|
|
1833
|
+
let offset = (() => {
|
|
1834
|
+
if (!existsSync(ledgerPath)) return 0;
|
|
1835
|
+
return readFileSync(ledgerPath).lastIndexOf(10) + 1;
|
|
1836
|
+
})();
|
|
1819
1837
|
const push = () => {
|
|
1820
|
-
const
|
|
1821
|
-
if (
|
|
1822
|
-
|
|
1823
|
-
|
|
1838
|
+
const end = size();
|
|
1839
|
+
if (end < offset) {
|
|
1840
|
+
offset = 0;
|
|
1841
|
+
res.write("event: reset\ndata: {}\n\n");
|
|
1842
|
+
}
|
|
1843
|
+
const fresh = [];
|
|
1844
|
+
if (end > offset) {
|
|
1845
|
+
const bytes = Buffer.alloc(end - offset);
|
|
1846
|
+
const fd = openSync(ledgerPath, "r");
|
|
1847
|
+
try {
|
|
1848
|
+
readSync(fd, bytes, 0, bytes.length, offset);
|
|
1849
|
+
} finally {
|
|
1850
|
+
closeSync(fd);
|
|
1851
|
+
}
|
|
1852
|
+
let at = 0;
|
|
1853
|
+
for (;;) {
|
|
1854
|
+
const nl = bytes.indexOf(10, at);
|
|
1855
|
+
if (nl === -1) break;
|
|
1856
|
+
const l = bytes.subarray(at, nl).toString("utf8");
|
|
1857
|
+
if (l.trim() !== "") try {
|
|
1858
|
+
fresh.push(JSON.parse(l));
|
|
1859
|
+
} catch {
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
at = nl + 1;
|
|
1863
|
+
}
|
|
1864
|
+
offset += at;
|
|
1865
|
+
}
|
|
1824
1866
|
if (fresh.length > 0) res.write(`data: ${JSON.stringify(fresh)}\n\n`);
|
|
1825
1867
|
else res.write(": keepalive\n\n");
|
|
1826
1868
|
};
|
|
1827
|
-
|
|
1869
|
+
let soon;
|
|
1870
|
+
const schedule = () => {
|
|
1871
|
+
soon ??= setTimeout(() => {
|
|
1872
|
+
soon = void 0;
|
|
1873
|
+
push();
|
|
1874
|
+
}, 250);
|
|
1875
|
+
};
|
|
1876
|
+
const watcher = existsSync(dir) ? watch(dir, { persistent: false }, schedule) : void 0;
|
|
1828
1877
|
const keep = setInterval(push, 2e3);
|
|
1829
1878
|
res.on("close", () => {
|
|
1830
1879
|
clearInterval(keep);
|
|
1880
|
+
if (soon !== void 0) clearTimeout(soon);
|
|
1831
1881
|
watcher?.close();
|
|
1832
1882
|
});
|
|
1833
1883
|
return true;
|
|
@@ -1851,11 +1901,32 @@ function handleApi(req, res, record) {
|
|
|
1851
1901
|
json(readRedteam(dir, files));
|
|
1852
1902
|
return true;
|
|
1853
1903
|
}
|
|
1904
|
+
if (route === "/api/status") {
|
|
1905
|
+
json({
|
|
1906
|
+
dir,
|
|
1907
|
+
files: Object.fromEntries(Object.entries(files).map(([name, file]) => {
|
|
1908
|
+
const path = resolve(dir, file);
|
|
1909
|
+
if (!existsSync(path)) return [name, null];
|
|
1910
|
+
const stat$1 = statSync(path);
|
|
1911
|
+
return [name, {
|
|
1912
|
+
size: stat$1.size,
|
|
1913
|
+
mtime: stat$1.mtimeMs
|
|
1914
|
+
}];
|
|
1915
|
+
}))
|
|
1916
|
+
});
|
|
1917
|
+
return true;
|
|
1918
|
+
}
|
|
1854
1919
|
const snapshot = readRecord(dir, files);
|
|
1855
1920
|
switch (route) {
|
|
1856
|
-
case "/api/record":
|
|
1857
|
-
|
|
1921
|
+
case "/api/record": {
|
|
1922
|
+
const over = snapshot.ledger.length - RECORD_WINDOW;
|
|
1923
|
+
json(over > 0 ? {
|
|
1924
|
+
...snapshot,
|
|
1925
|
+
ledger: snapshot.ledger.slice(over),
|
|
1926
|
+
trimmed: over
|
|
1927
|
+
} : snapshot);
|
|
1858
1928
|
break;
|
|
1929
|
+
}
|
|
1859
1930
|
case "/api/ledger":
|
|
1860
1931
|
json(snapshot.ledger);
|
|
1861
1932
|
break;
|
|
@@ -1871,20 +1942,6 @@ function handleApi(req, res, record) {
|
|
|
1871
1942
|
case "/api/pack":
|
|
1872
1943
|
json(snapshot.pack);
|
|
1873
1944
|
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
1945
|
default: json({ error: "not found" }, 404);
|
|
1889
1946
|
}
|
|
1890
1947
|
return true;
|
|
@@ -1901,6 +1958,7 @@ function serveConsole(root, port, record) {
|
|
|
1901
1958
|
res.end();
|
|
1902
1959
|
return;
|
|
1903
1960
|
}
|
|
1961
|
+
if ((req.url ?? "").startsWith("/api/")) apiVersion(res);
|
|
1904
1962
|
if (record !== void 0 && handleApi(req, res, record)) return;
|
|
1905
1963
|
const target = safePath(root, req.url ?? "/");
|
|
1906
1964
|
if (target === void 0) {
|
|
@@ -2052,6 +2110,21 @@ const USAGE = `⌜▪⌟ prae — operator policy for agent harnesses
|
|
|
2052
2110
|
--pack-dir adds the workspace's own packs (e.g. .prae/packs) beside
|
|
2053
2111
|
that pack, loaded and watched as a set; a set that would not load is
|
|
2054
2112
|
not mounted (see \`prae check <dir>\`).
|
|
2113
|
+
|
|
2114
|
+
prae init claude-code (--settings <file> | --scope user|project)
|
|
2115
|
+
[--shadow | --enforce] [--pack <file>] [--ledger <file>]
|
|
2116
|
+
[--dir <dir>] [--apply] [--remove]
|
|
2117
|
+
Register PRAE as a Claude Code PreToolUse hook: every tool call,
|
|
2118
|
+
built-in or MCP, judged by the pack and sealed in a signed ledger
|
|
2119
|
+
(default ~/.prae/claude-code/). Shadow (the default) records and
|
|
2120
|
+
never blocks; --enforce returns deny/ask to Claude Code. Prints the
|
|
2121
|
+
settings diff; writes only with --apply. --remove takes out PRAE's
|
|
2122
|
+
own entry and nothing else. See docs/pilot-claude-code.md.
|
|
2123
|
+
|
|
2124
|
+
prae hook claude-code [--shadow | --enforce] --pack <file> --ledger <file>
|
|
2125
|
+
[--key <private.pem>] [--settings <file>]
|
|
2126
|
+
The command that settings entry runs: reads one PreToolUse payload on
|
|
2127
|
+
stdin and answers through the hook's output contract.
|
|
2055
2128
|
`;
|
|
2056
2129
|
function fail(message) {
|
|
2057
2130
|
console.error(`prae: ${message}`);
|
|
@@ -2151,13 +2224,13 @@ async function scanFile(path, packPath, json) {
|
|
|
2151
2224
|
* not come up inspecting nothing.
|
|
2152
2225
|
*/
|
|
2153
2226
|
async function mcp(root, packPath, ledgerPath) {
|
|
2154
|
-
const { serveMcp } = await import("./mcp-
|
|
2227
|
+
const { serveMcp } = await import("./mcp-DdEDvTvR.js");
|
|
2155
2228
|
const { resolve: resolve$1 } = await import("node:path");
|
|
2156
2229
|
const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
|
|
2157
2230
|
const chosen = packPath ?? bundled;
|
|
2158
2231
|
if (chosen === void 0) return fail("mcp needs --pack <file>; no bundled secret-catcher found");
|
|
2159
2232
|
const pack = await loadPack(chosen);
|
|
2160
|
-
const { Ledger: Ledger$1 } = await import("./src-
|
|
2233
|
+
const { Ledger: Ledger$1 } = await import("./src-2iJy5MZc.js");
|
|
2161
2234
|
const ledger = ledgerPath === void 0 ? void 0 : Ledger$1.open(ledgerPath);
|
|
2162
2235
|
console.error(`prae mcp: serving ${resolve$1(root)} under ${pack.pack} ${pack.version}` + (ledgerPath === void 0 ? " (no ledger)" : ` · ledger ${ledgerPath}`));
|
|
2163
2236
|
await serveMcp({
|
|
@@ -2209,7 +2282,7 @@ async function explain(path, tool, pairs) {
|
|
|
2209
2282
|
* a key that arrived beside it is checked against nothing.
|
|
2210
2283
|
*/
|
|
2211
2284
|
async function keygen(dir) {
|
|
2212
|
-
const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-
|
|
2285
|
+
const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
|
|
2213
2286
|
const { writeFileSync: writeFileSync$1 } = await import("node:fs");
|
|
2214
2287
|
const keys = generateKeypair();
|
|
2215
2288
|
const priv = join(dir, "prae-signing.pem");
|
|
@@ -2232,7 +2305,7 @@ async function keygen(dir) {
|
|
|
2232
2305
|
async function keygenSealed(role, dir) {
|
|
2233
2306
|
if (role !== "operator" && role !== "policy" && role !== "ledger") fail("--role is operator, policy or ledger");
|
|
2234
2307
|
const stem = role === "ledger" ? "prae-signing" : role;
|
|
2235
|
-
const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-
|
|
2308
|
+
const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
|
|
2236
2309
|
const { writeFileSync: writeFileSync$1 } = await import("node:fs");
|
|
2237
2310
|
const keys = sealedKeypair(process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: "));
|
|
2238
2311
|
const pub = join(dir, `${stem}.pub.pem`);
|
|
@@ -2245,11 +2318,11 @@ async function keygenSealed(role, dir) {
|
|
|
2245
2318
|
}
|
|
2246
2319
|
/** A detached signature over a pack's bytes (PRA-107). */
|
|
2247
2320
|
async function signPack(packPath, keyPath) {
|
|
2248
|
-
const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-
|
|
2321
|
+
const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
|
|
2249
2322
|
const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
|
|
2250
2323
|
const bytes = readFileSync(packPath);
|
|
2251
|
-
const parsed = parsePack(bytes.toString("utf8"));
|
|
2252
|
-
if (!parsed.ok) fail(`${packPath} does not parse; nothing signed`);
|
|
2324
|
+
const parsed$1 = parsePack(bytes.toString("utf8"));
|
|
2325
|
+
if (!parsed$1.ok) fail(`${packPath} does not parse; nothing signed`);
|
|
2253
2326
|
const passphrase = process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: ");
|
|
2254
2327
|
let key;
|
|
2255
2328
|
try {
|
|
@@ -2262,7 +2335,7 @@ async function signPack(packPath, keyPath) {
|
|
|
2262
2335
|
type: "spki",
|
|
2263
2336
|
format: "pem"
|
|
2264
2337
|
});
|
|
2265
|
-
console.log(`signed ${packPath}.sig (${parsed.pack.pack} ${parsed.pack.version})`);
|
|
2338
|
+
console.log(`signed ${packPath}.sig (${parsed$1.pack.pack} ${parsed$1.pack.version})`);
|
|
2266
2339
|
console.log(`policy key ${fingerprint$1(pub.toString())}`);
|
|
2267
2340
|
}
|
|
2268
2341
|
/** Read a line from the terminal without echoing it. */
|
|
@@ -2290,7 +2363,7 @@ function askHidden(prompt) {
|
|
|
2290
2363
|
});
|
|
2291
2364
|
}
|
|
2292
2365
|
async function receipt(path, seq, keyPath, out) {
|
|
2293
|
-
const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-
|
|
2366
|
+
const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
|
|
2294
2367
|
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
2295
2368
|
const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
|
|
2296
2369
|
const privateKey = readFileSync$1(keyPath, "utf8");
|
|
@@ -2320,14 +2393,14 @@ async function receipt(path, seq, keyPath, out) {
|
|
|
2320
2393
|
* file and sets the exit code, so scripts can branch on it.
|
|
2321
2394
|
*/
|
|
2322
2395
|
async function verifyReceiptCmd(path) {
|
|
2323
|
-
const { checkReceipt } = await import("./receipt-check-
|
|
2324
|
-
let parsed;
|
|
2396
|
+
const { checkReceipt } = await import("./receipt-check-2F4zLEW5.js");
|
|
2397
|
+
let parsed$1;
|
|
2325
2398
|
try {
|
|
2326
|
-
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
2399
|
+
parsed$1 = JSON.parse(await readFile(path, "utf8"));
|
|
2327
2400
|
} catch {
|
|
2328
2401
|
return fail(`cannot read a receipt from ${path}`);
|
|
2329
2402
|
}
|
|
2330
|
-
const result = checkReceipt(parsed);
|
|
2403
|
+
const result = checkReceipt(parsed$1);
|
|
2331
2404
|
for (const line of result.lines) if (result.ok) console.log(line);
|
|
2332
2405
|
else console.error(line);
|
|
2333
2406
|
if (!result.ok) process.exit(1);
|
|
@@ -2345,14 +2418,14 @@ async function test(suitePath, packOverride, asJson) {
|
|
|
2345
2418
|
} catch {
|
|
2346
2419
|
return fail(`cannot read ${suitePath}`);
|
|
2347
2420
|
}
|
|
2348
|
-
let parsed;
|
|
2421
|
+
let parsed$1;
|
|
2349
2422
|
try {
|
|
2350
|
-
parsed = JSON.parse(raw);
|
|
2423
|
+
parsed$1 = JSON.parse(raw);
|
|
2351
2424
|
} catch (error) {
|
|
2352
2425
|
return fail(`${suitePath} is not valid JSON: ${error instanceof Error ? error.message : error}`);
|
|
2353
2426
|
}
|
|
2354
|
-
const { parseSuite, renderSuite, runSuite } = await import("./test-run-
|
|
2355
|
-
const suite = parseSuite(parsed);
|
|
2427
|
+
const { parseSuite, renderSuite, runSuite } = await import("./test-run-BGsK-inZ.js");
|
|
2428
|
+
const suite = parseSuite(parsed$1);
|
|
2356
2429
|
if (!suite.ok) {
|
|
2357
2430
|
console.error(`prae: ${suitePath} is not a valid test suite`);
|
|
2358
2431
|
for (const message of suite.errors) console.error(` ${message}`);
|
|
@@ -2391,14 +2464,14 @@ async function verify$1(path, keyPath, checkpointPath) {
|
|
|
2391
2464
|
const last = entries.at(-1);
|
|
2392
2465
|
console.log(`${result.entries} entries · chain intact${last === void 0 ? "" : ` · seal ${seal(last)}`}`);
|
|
2393
2466
|
if (checkpointPath !== void 0) {
|
|
2394
|
-
const { checkCheckpoint } = await import("./checkpoint-
|
|
2467
|
+
const { checkCheckpoint } = await import("./checkpoint-BLOGdWFu.js");
|
|
2395
2468
|
const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
|
|
2396
2469
|
const verdict = checkCheckpoint(entries, JSON.parse(readFileSync(checkpointPath, "utf8")), publicKey);
|
|
2397
2470
|
for (const line of verdict.lines) (verdict.ok ? console.log : console.error)(`checkpoint: ${line}`);
|
|
2398
2471
|
if (!verdict.ok) process.exitCode = 1;
|
|
2399
2472
|
}
|
|
2400
2473
|
if (keyPath !== void 0) {
|
|
2401
|
-
const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-
|
|
2474
|
+
const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-2iJy5MZc.js");
|
|
2402
2475
|
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
2403
2476
|
const publicKey = readFileSync$1(keyPath, "utf8");
|
|
2404
2477
|
let signed = 0;
|
|
@@ -2476,7 +2549,7 @@ async function report(path, out, packPath) {
|
|
|
2476
2549
|
* that step is a proposal a human approves, and it starts from this list.
|
|
2477
2550
|
*/
|
|
2478
2551
|
async function instructions(from, asJson) {
|
|
2479
|
-
const { ingest } = await import("./src-
|
|
2552
|
+
const { ingest } = await import("./src-XukJfuml.js");
|
|
2480
2553
|
const result = ingest(from);
|
|
2481
2554
|
if (asJson) {
|
|
2482
2555
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -2512,7 +2585,7 @@ async function instructions(from, asJson) {
|
|
|
2512
2585
|
* on purpose rather than something that happened to their policy.
|
|
2513
2586
|
*/
|
|
2514
2587
|
async function propose(from, options) {
|
|
2515
|
-
const [{ ingest }, { extract }] = await Promise.all([import("./src-
|
|
2588
|
+
const [{ ingest }, { extract }] = await Promise.all([import("./src-XukJfuml.js"), import("./src-BoBCmiwj.js")]);
|
|
2516
2589
|
const read = ingest(from);
|
|
2517
2590
|
if (read.files.length === 0) {
|
|
2518
2591
|
console.log(`No instruction files found at or above ${from}.`);
|
|
@@ -2572,9 +2645,9 @@ async function consoleOut(path, argv) {
|
|
|
2572
2645
|
const instructionsDir = flag(argv, "instructions");
|
|
2573
2646
|
if (instructionsDir !== void 0) {
|
|
2574
2647
|
const [{ ingest }, { extract }, { renderInstructions }] = await Promise.all([
|
|
2575
|
-
import("./src-
|
|
2576
|
-
import("./src-
|
|
2577
|
-
import("./instructions-
|
|
2648
|
+
import("./src-XukJfuml.js"),
|
|
2649
|
+
import("./src-BoBCmiwj.js"),
|
|
2650
|
+
import("./instructions-fCJ4j7ce.js")
|
|
2578
2651
|
]);
|
|
2579
2652
|
const read = ingest(instructionsDir);
|
|
2580
2653
|
instructionsView = renderInstructions(data, read, extract(read.statements));
|
|
@@ -2615,15 +2688,18 @@ async function consoleOut(path, argv) {
|
|
|
2615
2688
|
console.log(`record at ${url}api/record · events at ${url}api/events`);
|
|
2616
2689
|
console.log("loopback only, read-only. ctrl-c to stop.");
|
|
2617
2690
|
}
|
|
2691
|
+
/** The bundled baseline: beside the built code, or in examples/ in source. */
|
|
2692
|
+
function bundledBaseline() {
|
|
2693
|
+
return [join(import.meta.dirname, "prae-baseline.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "prae-baseline.prae.json")].find((candidate) => existsSync(candidate));
|
|
2694
|
+
}
|
|
2618
2695
|
async function init(argv) {
|
|
2619
|
-
const
|
|
2620
|
-
const packPath = flag(argv, "pack") ?? bundledBaseline;
|
|
2696
|
+
const packPath = flag(argv, "pack") ?? bundledBaseline();
|
|
2621
2697
|
if (packPath === void 0) fail("init needs --pack <file>; no bundled baseline found");
|
|
2698
|
+
const pack = await loadPack(packPath);
|
|
2622
2699
|
if (flag(argv, "pack") === void 0) {
|
|
2623
|
-
console.log(
|
|
2700
|
+
console.log(`No --pack given; mounting the built-in baseline (${pack.rules.length} rules).`);
|
|
2624
2701
|
console.log(`See it with: prae check ${packPath}\n`);
|
|
2625
2702
|
}
|
|
2626
|
-
const pack = await loadPack(packPath);
|
|
2627
2703
|
const source = await readFile(packPath, "utf8");
|
|
2628
2704
|
const link = argv.includes("--link");
|
|
2629
2705
|
const builtin = link ? { packFile: resolve(packPath) } : { pack: source };
|
|
@@ -2704,7 +2780,7 @@ async function chainMatrix(path, argv) {
|
|
|
2704
2780
|
const catalogue = guardiansPath === void 0 ? [] : JSON.parse(readFileSync(guardiansPath, "utf8")).guardians;
|
|
2705
2781
|
const guardianOf = (rule) => rule === void 0 ? void 0 : catalogue.find((g) => g.rules.includes(rule) || (g.armed ?? []).includes(rule))?.name;
|
|
2706
2782
|
const keyPath = flag(argv, "key");
|
|
2707
|
-
const { verifySignature: verifySignature$1 } = await import("./src-
|
|
2783
|
+
const { verifySignature: verifySignature$1 } = await import("./src-2iJy5MZc.js");
|
|
2708
2784
|
const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
|
|
2709
2785
|
const matrix = integrityMatrix(entries, pack, guardianOf).map((m) => {
|
|
2710
2786
|
const entry = entries.find((e) => e.seq === m.seq);
|
|
@@ -2735,7 +2811,7 @@ async function chainMatrix(path, argv) {
|
|
|
2735
2811
|
if (!chainOk || sigFailed) process.exitCode = 1;
|
|
2736
2812
|
}
|
|
2737
2813
|
async function checkpoint(path, keyPath, out) {
|
|
2738
|
-
const { makeCheckpoint } = await import("./checkpoint-
|
|
2814
|
+
const { makeCheckpoint } = await import("./checkpoint-BLOGdWFu.js");
|
|
2739
2815
|
let ledger;
|
|
2740
2816
|
try {
|
|
2741
2817
|
ledger = Ledger.open(path);
|
|
@@ -2756,9 +2832,9 @@ async function checkpoint(path, keyPath, out) {
|
|
|
2756
2832
|
}
|
|
2757
2833
|
}
|
|
2758
2834
|
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");
|
|
2835
|
+
const { toOcsf, ndjson, sendToHec } = await import("./ocsf-BwWyM7hc.js");
|
|
2836
|
+
const { createRequire: createRequire$1 } = await import("node:module");
|
|
2837
|
+
const { version } = createRequire$1(import.meta.url)("../package.json");
|
|
2762
2838
|
let ledger;
|
|
2763
2839
|
try {
|
|
2764
2840
|
ledger = Ledger.open(path);
|
|
@@ -2885,7 +2961,26 @@ async function main(argv) {
|
|
|
2885
2961
|
...flag(rest, "out") === void 0 ? {} : { out: flag(rest, "out") }
|
|
2886
2962
|
});
|
|
2887
2963
|
}
|
|
2888
|
-
case "init":
|
|
2964
|
+
case "init":
|
|
2965
|
+
if (rest[0] === "claude-code") {
|
|
2966
|
+
const { initClaudeCode, stableNode } = await import("./init-BPlBJvA-.js");
|
|
2967
|
+
try {
|
|
2968
|
+
return initClaudeCode(rest.slice(1), {
|
|
2969
|
+
cwd: process.cwd(),
|
|
2970
|
+
node: stableNode(process.execPath, process.env.PATH),
|
|
2971
|
+
bin: resolve(process.argv[1] ?? ""),
|
|
2972
|
+
baseline: bundledBaseline()
|
|
2973
|
+
});
|
|
2974
|
+
} catch (error) {
|
|
2975
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
return init(rest);
|
|
2979
|
+
case "hook": {
|
|
2980
|
+
if (rest[0] !== "claude-code") fail("hook takes claude-code");
|
|
2981
|
+
const { runHook } = await import("./hook-DxLgNtc4.js");
|
|
2982
|
+
return runHook(rest.slice(1));
|
|
2983
|
+
}
|
|
2889
2984
|
case "--help":
|
|
2890
2985
|
case "-h":
|
|
2891
2986
|
case void 0:
|