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.
@@ -1,456 +1,16 @@
1
- import { n as hostOf, r as withinAny, t as evaluate } from "./evaluate-BqLxhYiP.js";
2
- import { a as renderPacks, c as renderNav, d as FRAMEWORKS, f as cite, i as renderOverview, l as renderShell, n as renderApprovals, o as CONTROLS, r as renderCompliance, s as esc, t as navFor, u as VERDICT_LABEL$1 } from "./views-D4UEfbzt.js";
3
- import { c as fingerprint, g as chain, m as GENESIS, n as readEntries, p as verifySignature, r as verifyEntries, s as verifyBytes, t as Ledger, v as seal } from "./src-LA7NdLKb.js";
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
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
- counts[entry.verdict] = (counts[entry.verdict] ?? 0) + 1;
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
- function readLedger(path) {
1205
- if (!existsSync(path)) return [];
1206
- const out = [];
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
- out.push(JSON.parse(line));
774
+ into.push(JSON.parse(line));
1211
775
  } catch {}
1212
776
  }
1213
- return out;
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
- ...control.name === void 0 ? {} : { name: control.name },
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.filter((e) => (scope.agents === "all" || scope.agents.includes(agentOf$1(e))) && (scope.sessions === "all" || e.session !== void 0 && scope.sessions.includes(e.session)) && inPeriod(e.at, scope.period, now));
1377
+ const admitted = admit(data.entries, scope, options.agent, now);
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
- function assessCompliance(record, data) {
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
- ...assess(data, scopeFor(record.dir, catalog), catalog, {
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 text = existsSync(ledgerPath) ? readFileSync(ledgerPath, "utf8") : "";
1758
- let entries = [];
1759
- try {
1760
- entries = readEntries(text);
1761
- } catch {
1762
- entries = [];
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 pack = set.packs.length > 0 ? set.effective : void 0;
1745
+ const deployments = deploymentsOf(entries);
1746
+ const refused = refusedDeployments(deployments);
1747
+ const kept = set.packs.filter((p) => !refused.has(p.sha256));
1748
+ const pack = kept.length === 0 ? void 0 : kept.length === set.packs.length ? set.effective : {
1749
+ ...set.effective,
1750
+ rules: kept.flatMap((p) => p.pack.rules)
1751
+ };
1766
1752
  const data = {
1767
1753
  ...pack === void 0 ? {} : { pack },
1768
- entries,
1769
- verification: verifyEntries(text),
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: assessCompliance(record, data),
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
- let sent = readRecord(dir, files).ledger.length;
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 rows = readRecord(dir, files).ledger;
1821
- if (rows.length < sent) sent = 0;
1822
- const fresh = rows.slice(sent);
1823
- sent = rows.length;
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
- const watcher = existsSync(dir) ? watch(dir, { persistent: false }, () => push()) : void 0;
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
- json(snapshot);
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-BCXbpqu4.js");
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-7X6dO7gC.js");
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-7X6dO7gC.js");
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-7X6dO7gC.js");
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-7X6dO7gC.js");
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-7X6dO7gC.js");
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-DQK_gVPc.js");
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-CCDgJ0Dp.js");
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-C_q_Rpa5.js");
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-7X6dO7gC.js");
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-d4c_n6wd.js");
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-d4c_n6wd.js"), import("./src-CBGZVvt7.js")]);
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-d4c_n6wd.js"),
2576
- import("./src-CBGZVvt7.js"),
2577
- import("./instructions-ya0LVnZy.js")
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 bundledBaseline = [join(import.meta.dirname, "prae-baseline.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "prae-baseline.prae.json")].find((candidate) => existsSync(candidate));
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("No --pack given; mounting the built-in baseline (8 rules).");
2700
+ console.log(`No --pack given; mounting the built-in baseline (${pack.rules.length} rules).`);
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-7X6dO7gC.js");
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-C_q_Rpa5.js");
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-DhVGbBUw.js");
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": return init(rest);
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: