prae-cli 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bin.js +2 -6
- package/lib/{checkpoint-C_q_Rpa5.js → checkpoint-BqJ8xwky.js} +1 -1
- package/lib/cli-BcAJlB9B.js +8 -0
- package/lib/{cli-B_B6-bUZ.js → cli-DPldZ2ND.js} +259 -526
- package/lib/docs/pilot-claude-code.md +191 -0
- package/lib/{evaluate-BqLxhYiP.js → evaluate-DhoGQw0Z.js} +23 -1
- package/lib/frameworks-BgZHGKiH.js +118 -0
- package/lib/hook-Gll2spTP.js +695 -0
- package/lib/index.js +6 -4
- package/lib/init-DRd-IRR6.js +266 -0
- package/lib/{instructions-ya0LVnZy.js → instructions-DihPiiC2.js} +2 -1
- package/lib/{mcp-BCXbpqu4.js → mcp-D521EoP6.js} +2 -2
- package/lib/pack-h5n43S0F.js +448 -0
- package/lib/playground.js +4 -0
- package/lib/{receipt-check-DQK_gVPc.js → receipt-check-CUuEjGHL.js} +1 -1
- package/lib/{src-LA7NdLKb.js → src-Cy-Acrgj.js} +104 -25
- package/lib/{src-CwBEFhww.js → src-DMoYsdfo.js} +1 -1
- package/lib/src-wG36kT6K.js +3 -0
- package/lib/{test-run-CCDgJ0Dp.js → test-run-C8dHjIE2.js} +1 -1
- package/lib/views-CUcixte0.js +1396 -0
- package/package.json +4 -4
- package/lib/src-7X6dO7gC.js +0 -3
- package/lib/views-D4UEfbzt.js +0 -1505
- /package/lib/{ocsf-DhVGbBUw.js → ocsf-DF9cMiQJ.js} +0 -0
- /package/lib/{src-CBGZVvt7.js → src-BlmsTGdY.js} +0 -0
- /package/lib/{src-B25SHWcz.js → src-BuaOiL2v.js} +0 -0
- /package/lib/{src-d4c_n6wd.js → src-CDYyZyeU.js} +0 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { t as FRAMEWORKS } from "./frameworks-BgZHGKiH.js";
|
|
2
|
+
|
|
3
|
+
//#region ../kernel/src/pack.ts
|
|
4
|
+
function isObject(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
var Check = class {
|
|
8
|
+
errors = [];
|
|
9
|
+
fail(path, message) {
|
|
10
|
+
this.errors.push({
|
|
11
|
+
path,
|
|
12
|
+
message
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/** Reject any key the shape does not define. */
|
|
16
|
+
keys(path, value, allowed) {
|
|
17
|
+
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(", ")}`);
|
|
18
|
+
}
|
|
19
|
+
text(path, value) {
|
|
20
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
21
|
+
this.fail(path, "expected a non-empty string");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/** A field that accepts one name or a list of them. */
|
|
27
|
+
names(path, value) {
|
|
28
|
+
if (typeof value === "string") return value;
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
const out = [];
|
|
31
|
+
value.forEach((entry, index) => {
|
|
32
|
+
const one = this.text(`${path}[${index}]`, entry);
|
|
33
|
+
if (one !== void 0) out.push(one);
|
|
34
|
+
});
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
this.fail(path, "expected a string or an array of strings");
|
|
38
|
+
}
|
|
39
|
+
globs(path, value) {
|
|
40
|
+
if (!Array.isArray(value)) {
|
|
41
|
+
this.fail(path, "expected an array of path globs");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const out = [];
|
|
45
|
+
value.forEach((entry, index) => {
|
|
46
|
+
const one = this.text(`${path}[${index}]`, entry);
|
|
47
|
+
if (one !== void 0) out.push(one);
|
|
48
|
+
});
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
number(path, value) {
|
|
52
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
53
|
+
this.fail(path, "expected a finite number");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function readCommandMatch(check, path, value) {
|
|
60
|
+
if (!isObject(value)) {
|
|
61
|
+
check.fail(path, "expected an object");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
check.keys(path, value, [
|
|
65
|
+
"name",
|
|
66
|
+
"token",
|
|
67
|
+
"anyToken",
|
|
68
|
+
"pipedTo",
|
|
69
|
+
"pipeline"
|
|
70
|
+
]);
|
|
71
|
+
if (value.name === void 0 && value.token === void 0 && value.anyToken === void 0 && value.pipedTo === void 0 && value.pipeline === void 0) check.fail(path, "expected `name`, `token`, `anyToken`, `pipedTo` or `pipeline`; a command matcher with none matches nothing");
|
|
72
|
+
const out = {};
|
|
73
|
+
if (value.token !== void 0) {
|
|
74
|
+
const tokens = check.names(`${path}.token`, value.token);
|
|
75
|
+
if (tokens !== void 0) out.token = tokens;
|
|
76
|
+
}
|
|
77
|
+
if (value.name !== void 0) {
|
|
78
|
+
const names = check.names(`${path}.name`, value.name);
|
|
79
|
+
if (names !== void 0) out.name = names;
|
|
80
|
+
}
|
|
81
|
+
if (value.anyToken !== void 0) {
|
|
82
|
+
const tokens = check.names(`${path}.anyToken`, value.anyToken);
|
|
83
|
+
if (tokens !== void 0) out.anyToken = tokens;
|
|
84
|
+
}
|
|
85
|
+
if (value.pipedTo !== void 0) {
|
|
86
|
+
const pipes = check.names(`${path}.pipedTo`, value.pipedTo);
|
|
87
|
+
if (pipes !== void 0) out.pipedTo = pipes;
|
|
88
|
+
}
|
|
89
|
+
if (value.pipeline !== void 0) {
|
|
90
|
+
const shapes = check.names(`${path}.pipeline`, value.pipeline);
|
|
91
|
+
if (shapes !== void 0) out.pipeline = shapes;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
function readPathMatch(check, path, value) {
|
|
96
|
+
if (!isObject(value)) {
|
|
97
|
+
check.fail(path, "expected an object");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
check.keys(path, value, [
|
|
101
|
+
"arg",
|
|
102
|
+
"outside",
|
|
103
|
+
"inside"
|
|
104
|
+
]);
|
|
105
|
+
if (value.outside === void 0 && value.inside === void 0) check.fail(path, "expected `inside` or `outside`; a path matcher with neither matches nothing");
|
|
106
|
+
const out = {};
|
|
107
|
+
if (value.arg !== void 0) {
|
|
108
|
+
const arg = check.text(`${path}.arg`, value.arg);
|
|
109
|
+
if (arg !== void 0) out.arg = arg;
|
|
110
|
+
}
|
|
111
|
+
if (value.outside !== void 0) {
|
|
112
|
+
const globs = check.globs(`${path}.outside`, value.outside);
|
|
113
|
+
if (globs !== void 0) out.outside = globs;
|
|
114
|
+
}
|
|
115
|
+
if (value.inside !== void 0) {
|
|
116
|
+
const globs = check.globs(`${path}.inside`, value.inside);
|
|
117
|
+
if (globs !== void 0) out.inside = globs;
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function readHostMatch(check, path, value) {
|
|
122
|
+
if (!isObject(value)) {
|
|
123
|
+
check.fail(path, "expected an object");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
check.keys(path, value, [
|
|
127
|
+
"arg",
|
|
128
|
+
"in",
|
|
129
|
+
"notIn"
|
|
130
|
+
]);
|
|
131
|
+
if (value.in === void 0 && value.notIn === void 0) check.fail(path, "expected `in` or `notIn`; a host matcher with neither matches nothing");
|
|
132
|
+
const out = {};
|
|
133
|
+
if (value.arg !== void 0) {
|
|
134
|
+
const arg = check.text(`${path}.arg`, value.arg);
|
|
135
|
+
if (arg !== void 0) out.arg = arg;
|
|
136
|
+
}
|
|
137
|
+
if (value.in !== void 0) {
|
|
138
|
+
const list = check.globs(`${path}.in`, value.in);
|
|
139
|
+
if (list !== void 0) out.in = list;
|
|
140
|
+
}
|
|
141
|
+
if (value.notIn !== void 0) {
|
|
142
|
+
const list = check.globs(`${path}.notIn`, value.notIn);
|
|
143
|
+
if (list !== void 0) out.notIn = list;
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
function readArgMatch(check, path, value) {
|
|
148
|
+
if (!isObject(value)) {
|
|
149
|
+
check.fail(path, "expected an object");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
check.keys(path, value, [
|
|
153
|
+
"name",
|
|
154
|
+
"above",
|
|
155
|
+
"below",
|
|
156
|
+
"equals",
|
|
157
|
+
"oneOf",
|
|
158
|
+
"present"
|
|
159
|
+
]);
|
|
160
|
+
const name = check.text(`${path}.name`, value.name);
|
|
161
|
+
if (name === void 0) return void 0;
|
|
162
|
+
const out = { name };
|
|
163
|
+
if (value.above !== void 0) {
|
|
164
|
+
const n = check.number(`${path}.above`, value.above);
|
|
165
|
+
if (n !== void 0) out.above = n;
|
|
166
|
+
}
|
|
167
|
+
if (value.below !== void 0) {
|
|
168
|
+
const n = check.number(`${path}.below`, value.below);
|
|
169
|
+
if (n !== void 0) out.below = n;
|
|
170
|
+
}
|
|
171
|
+
if (value.equals !== void 0) out.equals = value.equals;
|
|
172
|
+
if (value.oneOf !== void 0) if (!Array.isArray(value.oneOf)) check.fail(`${path}.oneOf`, "expected an array");
|
|
173
|
+
else out.oneOf = value.oneOf;
|
|
174
|
+
if (value.present !== void 0) if (typeof value.present !== "boolean") check.fail(`${path}.present`, "expected a boolean");
|
|
175
|
+
else out.present = value.present;
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
function readSecretsMatch(check, path, value) {
|
|
179
|
+
if (!isObject(value)) {
|
|
180
|
+
check.fail(path, "expected an object");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
check.keys(path, value, [
|
|
184
|
+
"types",
|
|
185
|
+
"minConfidence",
|
|
186
|
+
"atLeast"
|
|
187
|
+
]);
|
|
188
|
+
const out = {};
|
|
189
|
+
if (value.types !== void 0) {
|
|
190
|
+
const types = check.globs(`${path}.types`, value.types);
|
|
191
|
+
if (types !== void 0) out.types = types;
|
|
192
|
+
}
|
|
193
|
+
if (value.minConfidence !== void 0) {
|
|
194
|
+
const n = check.number(`${path}.minConfidence`, value.minConfidence);
|
|
195
|
+
if (n !== void 0) if (n < 0 || n > 1) check.fail(`${path}.minConfidence`, "expected a number between 0 and 1");
|
|
196
|
+
else out.minConfidence = n;
|
|
197
|
+
}
|
|
198
|
+
if (value.atLeast !== void 0) {
|
|
199
|
+
const n = check.number(`${path}.atLeast`, value.atLeast);
|
|
200
|
+
if (n !== void 0) if (!Number.isInteger(n) || n < 1) check.fail(`${path}.atLeast`, "expected an integer of at least 1");
|
|
201
|
+
else out.atLeast = n;
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
function readMatch(check, path, value) {
|
|
206
|
+
if (!isObject(value)) {
|
|
207
|
+
check.fail(path, "expected an object");
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
check.keys(path, value, [
|
|
211
|
+
"tool",
|
|
212
|
+
"capability",
|
|
213
|
+
"plugin",
|
|
214
|
+
"agent",
|
|
215
|
+
"command",
|
|
216
|
+
"path",
|
|
217
|
+
"host",
|
|
218
|
+
"arg",
|
|
219
|
+
"count",
|
|
220
|
+
"secrets"
|
|
221
|
+
]);
|
|
222
|
+
const out = {};
|
|
223
|
+
for (const key of [
|
|
224
|
+
"tool",
|
|
225
|
+
"capability",
|
|
226
|
+
"plugin",
|
|
227
|
+
"agent"
|
|
228
|
+
]) if (value[key] !== void 0) {
|
|
229
|
+
const names = check.names(`${path}.${key}`, value[key]);
|
|
230
|
+
if (names !== void 0) out[key] = names;
|
|
231
|
+
}
|
|
232
|
+
if (value.command !== void 0) {
|
|
233
|
+
const match = readCommandMatch(check, `${path}.command`, value.command);
|
|
234
|
+
if (match !== void 0) out.command = match;
|
|
235
|
+
}
|
|
236
|
+
if (value.path !== void 0) {
|
|
237
|
+
const match = readPathMatch(check, `${path}.path`, value.path);
|
|
238
|
+
if (match !== void 0) out.path = match;
|
|
239
|
+
}
|
|
240
|
+
if (value.host !== void 0) {
|
|
241
|
+
const match = readHostMatch(check, `${path}.host`, value.host);
|
|
242
|
+
if (match !== void 0) out.host = match;
|
|
243
|
+
}
|
|
244
|
+
if (value.arg !== void 0) if (!Array.isArray(value.arg)) check.fail(`${path}.arg`, "expected an array of argument matchers");
|
|
245
|
+
else {
|
|
246
|
+
const list = [];
|
|
247
|
+
value.arg.forEach((entry, index) => {
|
|
248
|
+
const one = readArgMatch(check, `${path}.arg[${index}]`, entry);
|
|
249
|
+
if (one !== void 0) list.push(one);
|
|
250
|
+
});
|
|
251
|
+
out.arg = list;
|
|
252
|
+
}
|
|
253
|
+
if (value.count !== void 0) if (!isObject(value.count)) check.fail(`${path}.count`, "expected an object");
|
|
254
|
+
else {
|
|
255
|
+
check.keys(`${path}.count`, value.count, ["above"]);
|
|
256
|
+
const above = check.number(`${path}.count.above`, value.count.above);
|
|
257
|
+
if (above !== void 0) out.count = { above };
|
|
258
|
+
}
|
|
259
|
+
if (value.secrets !== void 0) {
|
|
260
|
+
const match = readSecretsMatch(check, `${path}.secrets`, value.secrets);
|
|
261
|
+
if (match !== void 0) out.secrets = match;
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function readEffect(check, path, value) {
|
|
266
|
+
if (!isObject(value)) {
|
|
267
|
+
check.fail(path, "expected an object");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const kind = value.kind === "hold" ? "ask" : value.kind;
|
|
271
|
+
if (kind !== "deny" && kind !== "ask" && kind !== "correct" && kind !== "warn" && kind !== "allow") {
|
|
272
|
+
check.fail(`${path}.kind`, "expected `allow`, `deny`, `hold` (or `ask`), `correct`, or `warn`");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
check.keys(path, value, kind === "correct" ? [
|
|
276
|
+
"kind",
|
|
277
|
+
"set",
|
|
278
|
+
"because"
|
|
279
|
+
] : ["kind", "because"]);
|
|
280
|
+
const because = check.text(`${path}.because`, value.because);
|
|
281
|
+
if (because === void 0) return void 0;
|
|
282
|
+
if (kind === "correct") {
|
|
283
|
+
if (!isObject(value.set) || Object.keys(value.set).length === 0) {
|
|
284
|
+
check.fail(`${path}.set`, "expected a non-empty object of arguments to set");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
kind,
|
|
289
|
+
set: value.set,
|
|
290
|
+
because
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
kind,
|
|
295
|
+
because
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function readRule(check, path, value) {
|
|
299
|
+
if (!isObject(value)) {
|
|
300
|
+
check.fail(path, "expected an object");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
check.keys(path, value, [
|
|
304
|
+
"id",
|
|
305
|
+
"title",
|
|
306
|
+
"description",
|
|
307
|
+
"when",
|
|
308
|
+
"effect",
|
|
309
|
+
"frameworks"
|
|
310
|
+
]);
|
|
311
|
+
const id = check.text(`${path}.id`, value.id);
|
|
312
|
+
const effect = readEffect(check, `${path}.effect`, value.effect);
|
|
313
|
+
if (value.when === void 0) check.fail(`${path}.when`, "expected an object");
|
|
314
|
+
const when = readMatch(check, `${path}.when`, value.when ?? {});
|
|
315
|
+
if (id === void 0 || effect === void 0) return void 0;
|
|
316
|
+
const rule = {
|
|
317
|
+
id,
|
|
318
|
+
when,
|
|
319
|
+
effect
|
|
320
|
+
};
|
|
321
|
+
if (value.title !== void 0) {
|
|
322
|
+
const title = check.text(`${path}.title`, value.title);
|
|
323
|
+
if (title !== void 0) rule.title = title;
|
|
324
|
+
}
|
|
325
|
+
if (value.description !== void 0) {
|
|
326
|
+
const description = check.text(`${path}.description`, value.description);
|
|
327
|
+
if (description !== void 0) rule.description = description;
|
|
328
|
+
}
|
|
329
|
+
if (value.frameworks !== void 0) {
|
|
330
|
+
const frameworks = readFrameworks(check, `${path}.frameworks`, value.frameworks);
|
|
331
|
+
if (frameworks !== void 0) rule.frameworks = frameworks;
|
|
332
|
+
}
|
|
333
|
+
return rule;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* A citation names a framework edition and controls the registry knows. A
|
|
337
|
+
* typo here would be a rule that claims a mapping no view can render, so it
|
|
338
|
+
* is a pack error rather than an ignored key — the same line the rest of the
|
|
339
|
+
* parser takes.
|
|
340
|
+
*/
|
|
341
|
+
function readFrameworks(check, path, value) {
|
|
342
|
+
if (!isObject(value)) {
|
|
343
|
+
check.fail(path, "expected an object keyed by framework id");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const out = {};
|
|
347
|
+
for (const [framework, controls] of Object.entries(value)) {
|
|
348
|
+
const known = FRAMEWORKS[framework];
|
|
349
|
+
if (known === void 0) {
|
|
350
|
+
check.fail(`${path}.${framework}`, `unknown framework; expected one of ${Object.keys(FRAMEWORKS).map((k) => `\`${k}\``).join(", ")}`);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (!Array.isArray(controls) || !controls.every((c) => typeof c === "string")) {
|
|
354
|
+
check.fail(`${path}.${framework}`, "expected an array of control ids");
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const ids = controls;
|
|
358
|
+
for (const control of ids) if (known.controls[control] === void 0) check.fail(`${path}.${framework}`, `unknown control \`${control}\` in ${known.name}`);
|
|
359
|
+
if (ids.length > 0) out[framework] = [...new Set(ids)];
|
|
360
|
+
}
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
363
|
+
/** Parse and validate a pack. Never throws; every problem lands in `errors`. */
|
|
364
|
+
function parsePack(source) {
|
|
365
|
+
let raw;
|
|
366
|
+
try {
|
|
367
|
+
raw = JSON.parse(source);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return {
|
|
370
|
+
ok: false,
|
|
371
|
+
errors: [{
|
|
372
|
+
path: "",
|
|
373
|
+
message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
374
|
+
}]
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const check = new Check();
|
|
378
|
+
if (!isObject(raw)) return {
|
|
379
|
+
ok: false,
|
|
380
|
+
errors: [{
|
|
381
|
+
path: "",
|
|
382
|
+
message: "expected an object"
|
|
383
|
+
}]
|
|
384
|
+
};
|
|
385
|
+
check.keys("", raw, [
|
|
386
|
+
"prae",
|
|
387
|
+
"pack",
|
|
388
|
+
"version",
|
|
389
|
+
"description",
|
|
390
|
+
"owner",
|
|
391
|
+
"expires_at",
|
|
392
|
+
"baseline",
|
|
393
|
+
"rules"
|
|
394
|
+
]);
|
|
395
|
+
if (raw.prae !== "0.1") check.fail(".prae", "expected `\"0.1\"`; this is the only pack version this build reads");
|
|
396
|
+
const name = check.text(".pack", raw.pack);
|
|
397
|
+
const version = check.text(".version", raw.version);
|
|
398
|
+
const description = raw.description === void 0 ? void 0 : check.text(".description", raw.description);
|
|
399
|
+
const owner = raw.owner === void 0 ? void 0 : check.text(".owner", raw.owner);
|
|
400
|
+
const expires = raw.expires_at === void 0 ? void 0 : check.text(".expires_at", raw.expires_at);
|
|
401
|
+
if (expires !== void 0 && (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(expires) || !Number.isFinite(Date.parse(expires)))) check.fail(".expires_at", "expected an ISO 8601 time with a zone, e.g. 2026-10-05T12:00:00Z");
|
|
402
|
+
let baseline;
|
|
403
|
+
if (raw.baseline !== void 0) if (!isObject(raw.baseline)) check.fail(".baseline", "expected an object");
|
|
404
|
+
else {
|
|
405
|
+
check.keys(".baseline", raw.baseline, ["capabilities"]);
|
|
406
|
+
const caps = check.globs(".baseline.capabilities", raw.baseline.capabilities);
|
|
407
|
+
if (caps !== void 0) baseline = { capabilities: caps };
|
|
408
|
+
}
|
|
409
|
+
const rules = [];
|
|
410
|
+
if (!Array.isArray(raw.rules)) check.fail(".rules", "expected an array");
|
|
411
|
+
else {
|
|
412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
413
|
+
raw.rules.forEach((entry, index) => {
|
|
414
|
+
const rule = readRule(check, `rules[${index}]`, entry);
|
|
415
|
+
if (rule === void 0) return;
|
|
416
|
+
if (seen.has(rule.id)) {
|
|
417
|
+
check.fail(`rules[${index}].id`, `duplicate rule id \`${rule.id}\``);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
seen.add(rule.id);
|
|
421
|
+
rules.push(rule);
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (check.errors.length > 0) return {
|
|
425
|
+
ok: false,
|
|
426
|
+
errors: check.errors
|
|
427
|
+
};
|
|
428
|
+
if (name === void 0 || version === void 0) return {
|
|
429
|
+
ok: false,
|
|
430
|
+
errors: check.errors
|
|
431
|
+
};
|
|
432
|
+
return {
|
|
433
|
+
ok: true,
|
|
434
|
+
pack: {
|
|
435
|
+
prae: "0.1",
|
|
436
|
+
pack: name,
|
|
437
|
+
version,
|
|
438
|
+
...description === void 0 ? {} : { description },
|
|
439
|
+
...owner === void 0 ? {} : { owner },
|
|
440
|
+
...expires === void 0 ? {} : { expires_at: expires },
|
|
441
|
+
...baseline === void 0 ? {} : { baseline },
|
|
442
|
+
rules
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
//#endregion
|
|
448
|
+
export { parsePack as t };
|
package/lib/playground.js
CHANGED
|
@@ -984,6 +984,7 @@
|
|
|
984
984
|
"version",
|
|
985
985
|
"description",
|
|
986
986
|
"owner",
|
|
987
|
+
"expires_at",
|
|
987
988
|
"baseline",
|
|
988
989
|
"rules"
|
|
989
990
|
]);
|
|
@@ -992,6 +993,8 @@
|
|
|
992
993
|
const version = check.text(".version", raw.version);
|
|
993
994
|
const description = raw.description === void 0 ? void 0 : check.text(".description", raw.description);
|
|
994
995
|
const owner = raw.owner === void 0 ? void 0 : check.text(".owner", raw.owner);
|
|
996
|
+
const expires = raw.expires_at === void 0 ? void 0 : check.text(".expires_at", raw.expires_at);
|
|
997
|
+
if (expires !== void 0 && (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(expires) || !Number.isFinite(Date.parse(expires)))) check.fail(".expires_at", "expected an ISO 8601 time with a zone, e.g. 2026-10-05T12:00:00Z");
|
|
995
998
|
let baseline;
|
|
996
999
|
if (raw.baseline !== void 0) if (!isObject(raw.baseline)) check.fail(".baseline", "expected an object");
|
|
997
1000
|
else {
|
|
@@ -1030,6 +1033,7 @@
|
|
|
1030
1033
|
version,
|
|
1031
1034
|
...description === void 0 ? {} : { description },
|
|
1032
1035
|
...owner === void 0 ? {} : { owner },
|
|
1036
|
+
...expires === void 0 ? {} : { expires_at: expires },
|
|
1033
1037
|
...baseline === void 0 ? {} : { baseline },
|
|
1034
1038
|
rules
|
|
1035
1039
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { appendFileSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { createCipheriv, createDecipheriv, createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, scryptSync, sign, verify } from "node:crypto";
|
|
4
4
|
|
|
5
5
|
//#region ../ledger/src/entry.ts
|
|
@@ -178,13 +178,16 @@ function unsealPrivateKey(sealed, passphrase) {
|
|
|
178
178
|
if (match === null) throw new Error("not a PRAE sealed key (an older format?): make a new one with prae keygen --role");
|
|
179
179
|
const box = JSON.parse(Buffer.from((match[1] ?? "").replace(/\s/g, ""), "base64").toString("utf8"));
|
|
180
180
|
if (box.v !== 1 || box.kdf !== "scrypt") throw new Error("unknown sealing");
|
|
181
|
-
const
|
|
181
|
+
const wrap = scryptSync(passphrase, Buffer.from(String(box.salt), "base64"), 32, {
|
|
182
182
|
N: Number(box.N),
|
|
183
183
|
r: Number(box.r),
|
|
184
184
|
p: Number(box.p),
|
|
185
185
|
maxmem: SCRYPT.maxmem
|
|
186
|
-
})
|
|
187
|
-
|
|
186
|
+
});
|
|
187
|
+
const tag = Buffer.from(String(box.tag), "base64");
|
|
188
|
+
if (tag.length !== 16) throw new Error("sealed key: authentication tag is not 16 bytes");
|
|
189
|
+
const decipher = createDecipheriv("aes-256-gcm", wrap, Buffer.from(String(box.iv), "base64"), { authTagLength: 16 });
|
|
190
|
+
decipher.setAuthTag(tag);
|
|
188
191
|
return createPrivateKey({
|
|
189
192
|
key: Buffer.concat([decipher.update(Buffer.from(String(box.data), "base64")), decipher.final()]),
|
|
190
193
|
format: "der",
|
|
@@ -207,14 +210,15 @@ function readEntries(text) {
|
|
|
207
210
|
return text.split("\n").filter((line) => line.trim() !== "").map((line) => JSON.parse(line));
|
|
208
211
|
}
|
|
209
212
|
/** Re-derive every hash and confirm each entry names its predecessor. */
|
|
210
|
-
function verifyEntries(text) {
|
|
213
|
+
function verifyEntries(text, from) {
|
|
211
214
|
const problems = [];
|
|
212
|
-
let expectedPrev = GENESIS;
|
|
213
|
-
let expectedSeq = 0;
|
|
215
|
+
let expectedPrev = from?.prev ?? GENESIS;
|
|
216
|
+
let expectedSeq = from?.seq ?? 0;
|
|
214
217
|
let count = 0;
|
|
218
|
+
const offset = from?.line ?? 0;
|
|
215
219
|
const lines = text.split("\n").filter((line) => line.trim() !== "");
|
|
216
220
|
for (const [index, line] of lines.entries()) {
|
|
217
|
-
const at = index + 1;
|
|
221
|
+
const at = offset + index + 1;
|
|
218
222
|
count += 1;
|
|
219
223
|
let entry;
|
|
220
224
|
try {
|
|
@@ -301,6 +305,16 @@ const STALE_LOCK_MS = 1e4;
|
|
|
301
305
|
/** Run `fn` holding an exclusive lock file; synchronous, like the append. */
|
|
302
306
|
function withLock(lock, fn) {
|
|
303
307
|
const deadline = Date.now() + 3e4;
|
|
308
|
+
for (;;) {
|
|
309
|
+
const done = tryLock(lock, fn);
|
|
310
|
+
if (done !== BUSY) return done;
|
|
311
|
+
if (Date.now() > deadline) throw new Error(`prae ledger: ${lock} has been held for 30 s; refusing to append without it`);
|
|
312
|
+
Atomics.wait(SLEEP, 0, 0, 2);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const BUSY = Symbol("busy");
|
|
316
|
+
/** Run `fn` holding the lock if it is free now (or stale); `BUSY` if not. */
|
|
317
|
+
function tryLock(lock, fn) {
|
|
304
318
|
for (;;) {
|
|
305
319
|
let fd;
|
|
306
320
|
try {
|
|
@@ -313,9 +327,7 @@ function withLock(lock, fn) {
|
|
|
313
327
|
continue;
|
|
314
328
|
}
|
|
315
329
|
} catch {}
|
|
316
|
-
|
|
317
|
-
Atomics.wait(SLEEP, 0, 0, 2);
|
|
318
|
-
continue;
|
|
330
|
+
return BUSY;
|
|
319
331
|
}
|
|
320
332
|
try {
|
|
321
333
|
return fn();
|
|
@@ -361,19 +373,30 @@ var Ledger = class Ledger {
|
|
|
361
373
|
}
|
|
362
374
|
/** Append one decision and return the entry as written. */
|
|
363
375
|
append(record) {
|
|
364
|
-
return withLock(`${this.path}.lock`, () =>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
376
|
+
return withLock(`${this.path}.lock`, () => this.write(record));
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Append only if no other writer holds the lock right now; `undefined`
|
|
380
|
+
* when one does. For a process that must not block while it waits —
|
|
381
|
+
* one that answers other writers' signers (PRA-143), whose writers hold
|
|
382
|
+
* the lock until it answers.
|
|
383
|
+
*/
|
|
384
|
+
tryAppend(record) {
|
|
385
|
+
const done = tryLock(`${this.path}.lock`, () => this.write(record));
|
|
386
|
+
return done === BUSY ? void 0 : done;
|
|
387
|
+
}
|
|
388
|
+
write(record) {
|
|
389
|
+
const { seq, last } = tailOf(this.path);
|
|
390
|
+
const sealed = chain(record, seq, last);
|
|
391
|
+
const entry = this.signer !== void 0 ? {
|
|
392
|
+
...sealed,
|
|
393
|
+
sig: this.signer(sealed)
|
|
394
|
+
} : this.signWith === void 0 ? sealed : {
|
|
395
|
+
...sealed,
|
|
396
|
+
sig: signEntry(sealed, this.signWith)
|
|
397
|
+
};
|
|
398
|
+
appendFileSync(this.path, `${JSON.stringify(entry)}\n`);
|
|
399
|
+
return entry;
|
|
377
400
|
}
|
|
378
401
|
/** Every entry, in order. */
|
|
379
402
|
read() {
|
|
@@ -396,4 +419,60 @@ var Ledger = class Ledger {
|
|
|
396
419
|
const nap = new Int32Array(new SharedArrayBuffer(4));
|
|
397
420
|
|
|
398
421
|
//#endregion
|
|
399
|
-
|
|
422
|
+
//#region ../ledger/src/verify-cache.ts
|
|
423
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
424
|
+
const sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
425
|
+
function verifyGrowing(path, publicKey) {
|
|
426
|
+
const raw = readFileSync(path, "utf8");
|
|
427
|
+
const text = raw.slice(0, raw.lastIndexOf("\n") + 1);
|
|
428
|
+
const slot = `${path}\u0000${publicKey ?? ""}`;
|
|
429
|
+
const known = prefixes.get(slot);
|
|
430
|
+
const from = known !== void 0 && known.key === publicKey && text.length >= known.length && sha(text.slice(0, known.length)) === known.sha ? known : void 0;
|
|
431
|
+
const fresh = from === void 0 ? text : text.slice(from.length);
|
|
432
|
+
const verified = verifyEntries(fresh, from === void 0 ? void 0 : {
|
|
433
|
+
prev: from.head,
|
|
434
|
+
seq: from.seq,
|
|
435
|
+
line: from.entries
|
|
436
|
+
});
|
|
437
|
+
let checked = from?.checked ?? 0;
|
|
438
|
+
const failed = [...from?.failed ?? []];
|
|
439
|
+
let rows = [];
|
|
440
|
+
try {
|
|
441
|
+
rows = readEntries(fresh);
|
|
442
|
+
} catch {
|
|
443
|
+
rows = [];
|
|
444
|
+
}
|
|
445
|
+
if (publicKey !== void 0) for (const e of rows) {
|
|
446
|
+
if (e.sig === void 0) continue;
|
|
447
|
+
checked += 1;
|
|
448
|
+
if (!verifySignature(e, e.sig, publicKey)) failed.push(e.seq);
|
|
449
|
+
}
|
|
450
|
+
const entries = (from?.entries ?? 0) + verified.entries;
|
|
451
|
+
const problems = [...from?.problems ?? [], ...verified.problems];
|
|
452
|
+
const last = rows.at(-1);
|
|
453
|
+
if (rows.length === verified.entries) prefixes.set(slot, {
|
|
454
|
+
length: text.length,
|
|
455
|
+
sha: sha(text),
|
|
456
|
+
key: publicKey,
|
|
457
|
+
head: last?.hash ?? from?.head ?? GENESIS,
|
|
458
|
+
seq: last === void 0 ? from?.seq ?? 0 : last.seq + 1,
|
|
459
|
+
entries,
|
|
460
|
+
checked,
|
|
461
|
+
failed,
|
|
462
|
+
problems
|
|
463
|
+
});
|
|
464
|
+
else prefixes.delete(slot);
|
|
465
|
+
return {
|
|
466
|
+
ok: problems.length === 0,
|
|
467
|
+
entries,
|
|
468
|
+
problems,
|
|
469
|
+
signatures: publicKey === void 0 ? null : {
|
|
470
|
+
checked,
|
|
471
|
+
failed
|
|
472
|
+
},
|
|
473
|
+
text
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
export { canonical as _, verifyEntries as a, seal as b, unsealPrivateKey as c, generateKeypair as d, makeReceipt as f, GENESIS as g, verifySignature as h, tailOf as i, verifyBytes as l, verifyReceipt as m, Ledger as n, sealedKeypair as o, signEntry as p, readEntries as r, signBytes as s, verifyGrowing as t, fingerprint as u, chain as v, hashEntry as y };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as scan, i as looksBinary, n as MAX_SCAN_BYTES, r as entropy, t as DETECTORS } from "./src-
|
|
1
|
+
import { a as scan, i as looksBinary, n as MAX_SCAN_BYTES, r as entropy, t as DETECTORS } from "./src-BuaOiL2v.js";
|
|
2
2
|
|
|
3
3
|
export { DETECTORS, MAX_SCAN_BYTES, entropy, looksBinary, scan };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { _ as canonical, a as verifyEntries, b as seal, c as unsealPrivateKey, d as generateKeypair, f as makeReceipt, g as GENESIS, h as verifySignature, i as tailOf, l as verifyBytes, m as verifyReceipt, n as Ledger, o as sealedKeypair, p as signEntry, r as readEntries, s as signBytes, t as verifyGrowing, u as fingerprint, v as chain, y as hashEntry } from "./src-Cy-Acrgj.js";
|
|
2
|
+
|
|
3
|
+
export { GENESIS, Ledger, canonical, chain, fingerprint, generateKeypair, hashEntry, makeReceipt, readEntries, seal, sealedKeypair, signBytes, signEntry, tailOf, unsealPrivateKey, verifyBytes, verifyEntries, verifyGrowing, verifyReceipt, verifySignature };
|