prae-cli 0.9.1 → 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.
@@ -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
@@ -351,6 +351,7 @@
351
351
  * appending to a pack can never accidentally widen what an agent may do.
352
352
  */
353
353
  const STRENGTH = {
354
+ allow: 0,
354
355
  warn: 1,
355
356
  correct: 2,
356
357
  ask: 3,
@@ -482,6 +483,14 @@
482
483
  evaluated
483
484
  };
484
485
  }
486
+ const allowed = hits.find((hit) => hit.effect.kind === "allow");
487
+ if (allowed !== void 0) return {
488
+ kind: "allow",
489
+ rule: allowed.rule.id,
490
+ reason: allowed.effect.because,
491
+ trace,
492
+ evaluated
493
+ };
485
494
  return {
486
495
  kind: "allow",
487
496
  trace,
@@ -854,9 +863,9 @@
854
863
  check.fail(path, "expected an object");
855
864
  return;
856
865
  }
857
- const kind = value.kind;
858
- if (kind !== "deny" && kind !== "ask" && kind !== "correct" && kind !== "warn") {
859
- check.fail(`${path}.kind`, "expected `deny`, `ask`, `correct`, or `warn`");
866
+ const kind = value.kind === "hold" ? "ask" : value.kind;
867
+ if (kind !== "deny" && kind !== "ask" && kind !== "correct" && kind !== "warn" && kind !== "allow") {
868
+ check.fail(`${path}.kind`, "expected `allow`, `deny`, `hold` (or `ask`), `correct`, or `warn`");
860
869
  return;
861
870
  }
862
871
  check.keys(path, value, kind === "correct" ? [
@@ -890,6 +899,7 @@
890
899
  check.keys(path, value, [
891
900
  "id",
892
901
  "title",
902
+ "description",
893
903
  "when",
894
904
  "effect",
895
905
  "frameworks"
@@ -908,6 +918,10 @@
908
918
  const title = check.text(`${path}.title`, value.title);
909
919
  if (title !== void 0) rule.title = title;
910
920
  }
921
+ if (value.description !== void 0) {
922
+ const description = check.text(`${path}.description`, value.description);
923
+ if (description !== void 0) rule.description = description;
924
+ }
911
925
  if (value.frameworks !== void 0) {
912
926
  const frameworks = readFrameworks(check, `${path}.frameworks`, value.frameworks);
913
927
  if (frameworks !== void 0) rule.frameworks = frameworks;
@@ -968,12 +982,19 @@
968
982
  "prae",
969
983
  "pack",
970
984
  "version",
985
+ "description",
986
+ "owner",
987
+ "expires_at",
971
988
  "baseline",
972
989
  "rules"
973
990
  ]);
974
991
  if (raw.prae !== "0.1") check.fail(".prae", "expected `\"0.1\"`; this is the only pack version this build reads");
975
992
  const name = check.text(".pack", raw.pack);
976
993
  const version = check.text(".version", raw.version);
994
+ const description = raw.description === void 0 ? void 0 : check.text(".description", raw.description);
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");
977
998
  let baseline;
978
999
  if (raw.baseline !== void 0) if (!isObject(raw.baseline)) check.fail(".baseline", "expected an object");
979
1000
  else {
@@ -1010,6 +1031,9 @@
1010
1031
  prae: "0.1",
1011
1032
  pack: name,
1012
1033
  version,
1034
+ ...description === void 0 ? {} : { description },
1035
+ ...owner === void 0 ? {} : { owner },
1036
+ ...expires === void 0 ? {} : { expires_at: expires },
1013
1037
  ...baseline === void 0 ? {} : { baseline },
1014
1038
  rules
1015
1039
  }
@@ -1,4 +1,4 @@
1
- import { d as verifyReceipt, s as fingerprint } from "./src-D7g4lvaY.js";
1
+ import { m as verifyReceipt, u as fingerprint } from "./src-Cy-Acrgj.js";
2
2
 
3
3
  //#region src/receipt-check.ts
4
4
  /** Shape we require before we will call it a receipt at all. */