@armoriq/policy-ir 0.1.2 → 0.1.3

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,722 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/document-entry.ts
21
+ var document_entry_exports = {};
22
+ __export(document_entry_exports, {
23
+ Diagnostics: () => Diagnostics,
24
+ NEVER: () => NEVER,
25
+ SOURCE_OPERATORS: () => SOURCE_OPERATORS,
26
+ VERDICTS: () => VERDICTS,
27
+ parseDocument: () => parseDocument,
28
+ parseYaml: () => parseYaml,
29
+ serialize: () => serialize,
30
+ validate: () => validate
31
+ });
32
+ module.exports = __toCommonJS(document_entry_exports);
33
+
34
+ // src/document/parse.ts
35
+ var import_yaml = require("yaml");
36
+
37
+ // src/document/diagnostics.ts
38
+ var Diagnostics = class {
39
+ items = [];
40
+ error(code, path, message) {
41
+ this.items.push({ code, severity: "error", path, message });
42
+ }
43
+ warning(code, path, message) {
44
+ this.items.push({ code, severity: "warning", path, message });
45
+ }
46
+ get hasErrors() {
47
+ return this.items.some((d) => d.severity === "error");
48
+ }
49
+ };
50
+
51
+ // src/document/grammar.ts
52
+ var SOURCE_OPERATORS = [
53
+ "eq",
54
+ "neq",
55
+ "in",
56
+ "notIn",
57
+ "gt",
58
+ "gte",
59
+ "lt",
60
+ "lte",
61
+ "matches",
62
+ "notMatches",
63
+ "startsWith",
64
+ "glob",
65
+ "withinWorkspace",
66
+ "exists"
67
+ ];
68
+ var VERDICTS = ["allow", "hold", "block"];
69
+ var NEVER = "never";
70
+
71
+ // src/document/reader.ts
72
+ var Reader = class {
73
+ constructor(diag) {
74
+ this.diag = diag;
75
+ }
76
+ diag;
77
+ object(value, path, keys) {
78
+ if (!isPlainObject(value)) {
79
+ this.diag.error("expected_object", path, `expected a mapping, got ${describe(value)}`);
80
+ return null;
81
+ }
82
+ for (const key of Object.keys(value)) {
83
+ if (!keys.includes(key)) this.diag.error("unknown_key", `${path}/${key}`, `unknown key "${key}"`);
84
+ }
85
+ return value;
86
+ }
87
+ /** A mapping whose keys are user-chosen (rule keys, member keys). */
88
+ map(value, path) {
89
+ if (isPlainObject(value)) return value;
90
+ this.diag.error("expected_object", path, `expected a mapping, got ${describe(value)}`);
91
+ return null;
92
+ }
93
+ string(value, path) {
94
+ if (typeof value === "string" && value.length > 0) return value;
95
+ this.diag.error("expected_string", path, `expected a non-empty string, got ${describe(value)}`);
96
+ return null;
97
+ }
98
+ optionalString(value, path) {
99
+ return value === void 0 ? void 0 : this.string(value, path) ?? void 0;
100
+ }
101
+ boolean(value, path) {
102
+ if (value === void 0 || typeof value === "boolean") return value;
103
+ this.diag.error("expected_boolean", path, `expected true or false, got ${describe(value)}`);
104
+ return void 0;
105
+ }
106
+ number(value, path) {
107
+ if (value === void 0 || typeof value === "number" && Number.isFinite(value)) return value;
108
+ this.diag.error("expected_number", path, `expected a number, got ${describe(value)}`);
109
+ return void 0;
110
+ }
111
+ strings(value, path) {
112
+ if (value === void 0) return void 0;
113
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) return value;
114
+ this.diag.error("expected_string_list", path, `expected a list of strings, got ${describe(value)}`);
115
+ return void 0;
116
+ }
117
+ verdict(value, path) {
118
+ if (typeof value === "string" && VERDICTS.includes(value)) return value;
119
+ this.diag.error("expected_verdict", path, `expected allow, hold or block, got ${describe(value)}`);
120
+ return null;
121
+ }
122
+ optionalVerdict(value, path) {
123
+ return value === void 0 ? void 0 : this.verdict(value, path) ?? void 0;
124
+ }
125
+ /** `never` or ISO-8601; absent materializes to `never` (VC-7c, FR-22). */
126
+ expiresAt(value, path) {
127
+ if (value === void 0) return NEVER;
128
+ if (value instanceof Date) return value.toISOString();
129
+ if (value === NEVER) return NEVER;
130
+ if (typeof value === "string" && !Number.isNaN(Date.parse(value))) return new Date(value).toISOString();
131
+ this.diag.error("expected_expiry", path, `expected "never" or an ISO-8601 date-time, got ${describe(value)}`);
132
+ return NEVER;
133
+ }
134
+ };
135
+ function isPlainObject(value) {
136
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
137
+ }
138
+ function describe(value) {
139
+ if (value === null) return "null";
140
+ if (Array.isArray(value)) return "a list";
141
+ if (value instanceof Date) return "a date";
142
+ return typeof value === "object" ? "a mapping" : JSON.stringify(value);
143
+ }
144
+
145
+ // src/document/parse.ts
146
+ function parseYaml(text) {
147
+ const diag = new Diagnostics();
148
+ let value;
149
+ try {
150
+ value = (0, import_yaml.parse)(text);
151
+ } catch (error) {
152
+ diag.error("yaml_syntax", "", error.message);
153
+ return { source: null, diagnostics: diag };
154
+ }
155
+ return parseValue(value, diag);
156
+ }
157
+ function parseDocument(value) {
158
+ return parseValue(value, new Diagnostics());
159
+ }
160
+ var ROOT_KEYS = ["apiVersion", "kind", "metadata", "target", "precedence", "spec"];
161
+ var SPEC_KEYS = ["tools", "files", "network", "skills", "secrets", "members", "products", "capture", "approvalDefaults", "expiresAt"];
162
+ var VARIANT_KEYS = ["action", "when", "approval", "reason", "controls", "expiresAt"];
163
+ function parseValue(value, diag) {
164
+ const r = new Reader(diag);
165
+ const root = r.object(value, "", ROOT_KEYS);
166
+ if (!root) return { source: null, diagnostics: diag };
167
+ if (root.apiVersion !== "armor.io/v2") diag.error("unsupported_api_version", "/apiVersion", `expected armor.io/v2`);
168
+ if (root.kind !== "Policy") diag.error("unsupported_kind", "/kind", `expected Policy`);
169
+ const metadata = parseMetadata(r, root.metadata);
170
+ const target = parseTarget(r, diag, root.target);
171
+ const precedence = parsePrecedence(diag, root.precedence);
172
+ const spec = parseSpec(r, diag, root.spec);
173
+ if (diag.hasErrors || !metadata || !target || !spec) return { source: null, diagnostics: diag };
174
+ const source = { apiVersion: "armor.io/v2", kind: "Policy", metadata, target, spec };
175
+ if (precedence) source.precedence = precedence;
176
+ return { source, diagnostics: diag };
177
+ }
178
+ function parseMetadata(r, value) {
179
+ const m = r.object(value, "/metadata", ["name", "description", "orgId", "labels"]);
180
+ if (!m) return null;
181
+ const name = r.string(m.name, "/metadata/name");
182
+ if (!name) return null;
183
+ const out = { name };
184
+ const description = r.optionalString(m.description, "/metadata/description");
185
+ const orgId = r.optionalString(m.orgId, "/metadata/orgId");
186
+ if (description) out.description = description;
187
+ if (orgId) out.orgId = orgId;
188
+ if (m.labels !== void 0) out.labels = parseLabels(r, m.labels);
189
+ return out;
190
+ }
191
+ function parseLabels(r, value) {
192
+ const labels = r.map(value, "/metadata/labels") ?? {};
193
+ const out = {};
194
+ for (const [k, v] of Object.entries(labels)) {
195
+ const s = r.string(v, `/metadata/labels/${k}`);
196
+ if (s) out[k] = s;
197
+ }
198
+ return out;
199
+ }
200
+ var TARGET_FORMS = ["agent", "group", "org", "device"];
201
+ function parseTarget(r, diag, value) {
202
+ const t = r.object(value, "/target", ["agent", "mcpServers", "group", "org", "device"]);
203
+ if (!t) return null;
204
+ const forms = TARGET_FORMS.filter((f) => t[f] !== void 0);
205
+ if (forms.length !== 1) {
206
+ diag.error("target_form", "/target", `exactly one of agent, group, org or device is required; got ${forms.length}`);
207
+ return null;
208
+ }
209
+ return targetOf(r, diag, t, forms[0]);
210
+ }
211
+ function targetOf(r, diag, t, form) {
212
+ if (form === "org" || form === "device") return fixedTarget(diag, t, form);
213
+ if (t.mcpServers !== void 0 && form !== "agent") diag.error("unknown_key", "/target/mcpServers", "mcpServers belongs to an agent target");
214
+ const id = r.string(t[form], `/target/${form}`);
215
+ if (!id) return null;
216
+ if (form === "group") return { group: id };
217
+ const servers = r.strings(t.mcpServers, "/target/mcpServers");
218
+ return servers ? { agent: id, mcpServers: servers } : { agent: id };
219
+ }
220
+ var FIXED_TARGETS = {
221
+ org: { value: "*", code: "target_org", message: 'the organization floor is written as org: "*"' },
222
+ device: { value: "local", code: "target_device", message: "the only device target is local" }
223
+ };
224
+ function fixedTarget(diag, t, form) {
225
+ const expected = FIXED_TARGETS[form];
226
+ if (t[form] === expected.value) return form === "org" ? { org: "*" } : { device: "local" };
227
+ diag.error(expected.code, `/target/${form}`, expected.message);
228
+ return null;
229
+ }
230
+ function parsePrecedence(diag, value) {
231
+ if (value === void 0 || value === "device-first" || value === "agent-first") return value;
232
+ diag.error("expected_precedence", "/precedence", "expected device-first or agent-first");
233
+ return void 0;
234
+ }
235
+ function parseSpec(r, diag, value) {
236
+ const s = r.object(value, "/spec", SPEC_KEYS);
237
+ if (!s) return null;
238
+ const tools = parseTools(r, diag, s.tools, "/spec/tools");
239
+ if (!tools) return null;
240
+ const spec = { tools, expiresAt: r.expiresAt(s.expiresAt, "/spec/expiresAt") };
241
+ assign(spec, "files", parseFiles(r, s.files));
242
+ assign(spec, "network", parseNetwork(r, s.network));
243
+ assign(spec, "skills", parseSkills(r, s.skills));
244
+ assign(spec, "secrets", parseSecrets(r, s.secrets));
245
+ assign(spec, "members", parseOverlays(r, diag, s.members, "/spec/members"));
246
+ assign(spec, "products", parseOverlays(r, diag, s.products, "/spec/products"));
247
+ assign(spec, "capture", parseCapture(r, s.capture));
248
+ assign(spec, "approvalDefaults", parseApproval(r, s.approvalDefaults, "/spec/approvalDefaults"));
249
+ return spec;
250
+ }
251
+ function assign(spec, key, value) {
252
+ if (value !== void 0) spec[key] = value;
253
+ }
254
+ function parseTools(r, diag, value, path) {
255
+ const t = r.object(value, path, ["default", "rules"]);
256
+ if (!t) return null;
257
+ const dflt = r.verdict(t.default, `${path}/default`);
258
+ if (!dflt) return null;
259
+ const rules = parseRules(r, diag, t.rules, `${path}/rules`);
260
+ return rules ? { default: dflt, rules } : { default: dflt };
261
+ }
262
+ function parseRules(r, diag, value, path) {
263
+ if (value === void 0) return void 0;
264
+ const map = r.map(value, path);
265
+ if (!map) return void 0;
266
+ const out = {};
267
+ for (const [key, rule] of Object.entries(map)) {
268
+ const parsed = parseRule(r, diag, rule, `${path}/${key}`);
269
+ if (parsed) out[key] = parsed;
270
+ }
271
+ return out;
272
+ }
273
+ function parseRule(r, diag, value, path) {
274
+ if (typeof value === "string") {
275
+ const v = r.verdict(value, path);
276
+ return v ? [{ action: v, expiresAt: "never" }] : null;
277
+ }
278
+ if (!Array.isArray(value)) {
279
+ diag.error("expected_rule", path, "expected a verdict or a list of variants");
280
+ return null;
281
+ }
282
+ const variants = value.map((v, i) => parseVariant(r, diag, v, `${path}/${i}`));
283
+ return variants.every((v) => v !== null) ? variants : null;
284
+ }
285
+ function parseVariant(r, diag, value, path) {
286
+ const v = r.object(value, path, VARIANT_KEYS);
287
+ if (!v) return null;
288
+ const action = r.verdict(v.action, `${path}/action`);
289
+ if (!action) return null;
290
+ const variant = { action, expiresAt: r.expiresAt(v.expiresAt, `${path}/expiresAt`) };
291
+ const when = parseWhen(r, diag, v.when, `${path}/when`);
292
+ const approval = parseApproval(r, v.approval, `${path}/approval`);
293
+ const reason = r.optionalString(v.reason, `${path}/reason`);
294
+ const controls = r.strings(v.controls, `${path}/controls`);
295
+ if (when) variant.when = when;
296
+ if (approval) variant.approval = approval;
297
+ if (reason) variant.reason = reason;
298
+ if (controls) variant.controls = controls;
299
+ return variant;
300
+ }
301
+ function parseWhen(r, diag, value, path) {
302
+ if (value === void 0) return void 0;
303
+ const map = r.map(value, path);
304
+ if (!map) return void 0;
305
+ const out = {};
306
+ for (const [field, spec] of Object.entries(map)) {
307
+ const cond = parseCondition(r, diag, spec, `${path}/${field}`);
308
+ if (cond) out[field] = cond;
309
+ }
310
+ return out;
311
+ }
312
+ function parseCondition(r, diag, value, path) {
313
+ const c = r.object(value, path, SOURCE_OPERATORS);
314
+ if (!c) return null;
315
+ const ops = Object.keys(c);
316
+ if (ops.length !== 1) {
317
+ diag.error("condition_operator", path, `exactly one operator is required; got ${ops.length}`);
318
+ return null;
319
+ }
320
+ return c;
321
+ }
322
+ function parseApproval(r, value, path) {
323
+ if (value === void 0) return void 0;
324
+ const a = r.object(value, path, ["approvers", "roles", "members", "anyMember"]);
325
+ if (!a) return void 0;
326
+ const approvers = r.number(a.approvers, `${path}/approvers`);
327
+ const out = { approvers: approvers ?? 1 };
328
+ const roles = r.strings(a.roles, `${path}/roles`);
329
+ const members = r.strings(a.members, `${path}/members`);
330
+ const anyMember = r.boolean(a.anyMember, `${path}/anyMember`);
331
+ if (roles) out.roles = roles;
332
+ if (members) out.members = members;
333
+ if (anyMember !== void 0) out.anyMember = anyMember;
334
+ return out;
335
+ }
336
+ function parseFiles(r, value) {
337
+ if (value === void 0) return void 0;
338
+ const f = r.object(value, "/spec/files", ["deny", "allowOutsideWorkspace"]);
339
+ if (!f) return void 0;
340
+ const out = {};
341
+ const deny = r.strings(f.deny, "/spec/files/deny");
342
+ const outside = r.boolean(f.allowOutsideWorkspace, "/spec/files/allowOutsideWorkspace");
343
+ if (deny) out.deny = deny;
344
+ if (outside !== void 0) out.allowOutsideWorkspace = outside;
345
+ return out;
346
+ }
347
+ function parseNetwork(r, value) {
348
+ if (value === void 0) return void 0;
349
+ const n = r.object(value, "/spec/network", ["default", "allow", "deny"]);
350
+ if (!n) return void 0;
351
+ const out = {};
352
+ const dflt = r.optionalVerdict(n.default, "/spec/network/default");
353
+ const allow = parseNetworkEntries(r, n.allow, "/spec/network/allow");
354
+ const deny = parseNetworkEntries(r, n.deny, "/spec/network/deny");
355
+ if (dflt) out.default = dflt;
356
+ if (allow) out.allow = allow;
357
+ if (deny) out.deny = deny;
358
+ return out;
359
+ }
360
+ function parseNetworkEntries(r, value, path) {
361
+ if (value === void 0) return void 0;
362
+ if (!Array.isArray(value)) {
363
+ r.strings(value, path);
364
+ return void 0;
365
+ }
366
+ const out = [];
367
+ value.forEach((entry, i) => {
368
+ if (typeof entry === "string") return void out.push(entry);
369
+ const e = r.object(entry, `${path}/${i}`, ["host", "reason"]);
370
+ const host = e ? r.string(e.host, `${path}/${i}/host`) : null;
371
+ if (e && host) out.push(e.reason === void 0 ? { host } : { host, reason: String(e.reason) });
372
+ });
373
+ return out;
374
+ }
375
+ function parseSkills(r, value) {
376
+ if (value === void 0) return void 0;
377
+ const s = r.object(value, "/spec/skills", ["default", "hold", "block"]);
378
+ if (!s) return void 0;
379
+ const out = {};
380
+ const dflt = r.optionalVerdict(s.default, "/spec/skills/default");
381
+ const hold = r.strings(s.hold, "/spec/skills/hold");
382
+ const block = r.strings(s.block, "/spec/skills/block");
383
+ if (dflt) out.default = dflt;
384
+ if (hold) out.hold = hold;
385
+ if (block) out.block = block;
386
+ return out;
387
+ }
388
+ function parseSecrets(r, value) {
389
+ if (value === void 0) return void 0;
390
+ const s = r.object(value, "/spec/secrets", ["envBlocklist"]);
391
+ if (!s) return void 0;
392
+ const envBlocklist = r.strings(s.envBlocklist, "/spec/secrets/envBlocklist");
393
+ return envBlocklist ? { envBlocklist } : {};
394
+ }
395
+ function parseOverlays(r, diag, value, path) {
396
+ if (value === void 0) return void 0;
397
+ const map = r.map(value, path);
398
+ if (!map) return void 0;
399
+ const out = {};
400
+ for (const [key, overlay] of Object.entries(map)) {
401
+ const parsed = parseOverlay(r, diag, overlay, `${path}/${key}`);
402
+ if (parsed) out[key] = parsed;
403
+ }
404
+ return out;
405
+ }
406
+ function parseOverlay(r, diag, value, path) {
407
+ const o = r.object(value, path, ["tools", "expiresAt"]);
408
+ if (!o) return null;
409
+ const overlay = { expiresAt: r.expiresAt(o.expiresAt, `${path}/expiresAt`) };
410
+ if (o.tools === void 0) return overlay;
411
+ const t = r.object(o.tools, `${path}/tools`, ["default", "rules"]);
412
+ if (!t) return overlay;
413
+ const tools = {};
414
+ const dflt = r.optionalVerdict(t.default, `${path}/tools/default`);
415
+ const rules = parseRules(r, diag, t.rules, `${path}/tools/rules`);
416
+ if (dflt) tools.default = dflt;
417
+ if (rules) tools.rules = rules;
418
+ overlay.tools = tools;
419
+ return overlay;
420
+ }
421
+ function parseCapture(r, value) {
422
+ if (value === void 0) return void 0;
423
+ const c = r.object(value, "/spec/capture", ["arguments", "redact", "maxBytesPerCall", "sample"]);
424
+ if (!c) return void 0;
425
+ const out = {};
426
+ const args = r.boolean(c.arguments, "/spec/capture/arguments");
427
+ const redact = r.strings(c.redact, "/spec/capture/redact");
428
+ const max = r.number(c.maxBytesPerCall, "/spec/capture/maxBytesPerCall");
429
+ const sample = r.number(c.sample, "/spec/capture/sample");
430
+ if (args !== void 0) out.arguments = args;
431
+ if (redact) out.redact = redact;
432
+ if (max !== void 0) out.maxBytesPerCall = max;
433
+ if (sample !== void 0) out.sample = sample;
434
+ return out;
435
+ }
436
+
437
+ // src/evaluate/input.ts
438
+ var VERDICT_RANK = { allow: 0, hold: 1, block: 2 };
439
+
440
+ // src/document/portable-regex.ts
441
+ function isPortablePattern(pattern) {
442
+ if (typeof pattern !== "string") return false;
443
+ try {
444
+ new RegExp(pattern);
445
+ } catch {
446
+ return false;
447
+ }
448
+ const parser = new Parser(pattern);
449
+ return parser.alternatives() && parser.done();
450
+ }
451
+ var ESCAPED_PUNCTUATION = new Set("\\/.*+?()[]{}|^$-");
452
+ var CONTROL_ESCAPES = new Set("tnrfv");
453
+ var SET_ESCAPES = new Set("dwsDWS");
454
+ var CLASS_SET_ESCAPES = new Set("dws");
455
+ var BARE_SPECIALS = new Set("]{}*+?");
456
+ var QUANTIFIER = /^\{\d+(,\d*)?\}/;
457
+ var Parser = class {
458
+ constructor(p) {
459
+ this.p = p;
460
+ }
461
+ p;
462
+ i = 0;
463
+ done() {
464
+ return this.i === this.p.length;
465
+ }
466
+ alternatives() {
467
+ while (!this.done() && this.p[this.i] !== ")") {
468
+ if (this.p[this.i] === "|") this.i++;
469
+ else if (!this.term()) return false;
470
+ }
471
+ return true;
472
+ }
473
+ term() {
474
+ const atom = this.atom();
475
+ return atom !== null && this.quantifier(atom === "quantifiable");
476
+ }
477
+ atom() {
478
+ const c = this.p[this.i++];
479
+ if (c === "^" || c === "$") return "assertion";
480
+ if (c === "\\") return this.escape();
481
+ if (c === "[") return this.charClass() ? "quantifiable" : null;
482
+ if (c === "(") return this.group() ? "quantifiable" : null;
483
+ return BARE_SPECIALS.has(c) ? null : "quantifiable";
484
+ }
485
+ escape() {
486
+ const e = this.p[this.i++] ?? "";
487
+ if (e === "b" || e === "B") return "assertion";
488
+ if (SET_ESCAPES.has(e) || CONTROL_ESCAPES.has(e) || ESCAPED_PUNCTUATION.has(e)) return "quantifiable";
489
+ return this.hexEscape(e) ? "quantifiable" : null;
490
+ }
491
+ hexEscape(e) {
492
+ const width = e === "x" ? 2 : e === "u" ? 4 : 0;
493
+ const digits = this.p.slice(this.i, this.i + width);
494
+ if (width === 0 || !/^[0-9a-fA-F]+$/.test(digits) || digits.length !== width) return false;
495
+ this.i += width;
496
+ return true;
497
+ }
498
+ group() {
499
+ if (this.p[this.i] === "?") {
500
+ if (this.p[this.i + 1] !== ":") return false;
501
+ this.i += 2;
502
+ }
503
+ if (!this.alternatives() || this.p[this.i] !== ")") return false;
504
+ this.i++;
505
+ return true;
506
+ }
507
+ charClass() {
508
+ if (this.p[this.i] === "^") this.i++;
509
+ if (this.p[this.i] === "]") return false;
510
+ while (!this.done() && this.p[this.i] !== "]") {
511
+ if (!this.classItem()) return false;
512
+ }
513
+ if (this.done()) return false;
514
+ this.i++;
515
+ return true;
516
+ }
517
+ classItem() {
518
+ const start = this.classAtom();
519
+ if (start === null) return false;
520
+ if (this.p[this.i] !== "-" || this.p[this.i + 1] === "]") return true;
521
+ this.i++;
522
+ return start === "char" && this.classAtom() === "char";
523
+ }
524
+ classAtom() {
525
+ const c = this.p[this.i++];
526
+ if (c === "[") return null;
527
+ if (c !== "\\") return isSurrogate(c) ? null : "char";
528
+ const e = this.p[this.i++] ?? "";
529
+ if (CLASS_SET_ESCAPES.has(e)) return "set";
530
+ if (CONTROL_ESCAPES.has(e) || ESCAPED_PUNCTUATION.has(e)) return "char";
531
+ return this.hexEscape(e) ? "char" : null;
532
+ }
533
+ quantifier(quantifiable) {
534
+ const length = this.quantifierLength();
535
+ if (length === 0) return true;
536
+ if (length < 0 || !quantifiable) return false;
537
+ this.i += length;
538
+ if (this.p[this.i] === "?") this.i++;
539
+ return true;
540
+ }
541
+ quantifierLength() {
542
+ const c = this.p[this.i];
543
+ if (c === "*" || c === "+" || c === "?") return 1;
544
+ if (c !== "{") return 0;
545
+ return QUANTIFIER.exec(this.p.slice(this.i))?.[0].length ?? -1;
546
+ }
547
+ };
548
+ function isSurrogate(c) {
549
+ const code = c.charCodeAt(0);
550
+ return code >= 55296 && code <= 57343;
551
+ }
552
+
553
+ // src/document/validate.ts
554
+ function validate(source) {
555
+ const diag = new Diagnostics();
556
+ const ctx = { diag, approvalDefaults: source.spec.approvalDefaults, base: source.spec.tools };
557
+ validateTargetSections(ctx, source);
558
+ if (source.spec.approvalDefaults) validateApproval(ctx, source.spec.approvalDefaults, "/spec/approvalDefaults", true);
559
+ validateRules(ctx, source.spec.tools.rules, "/spec/tools/rules");
560
+ validateOverlays(ctx, source.spec.members, "/spec/members");
561
+ validateOverlays(ctx, source.spec.products, "/spec/products");
562
+ validateCapture(ctx, source);
563
+ return diag;
564
+ }
565
+ function validateTargetSections(ctx, source) {
566
+ const local = "device" in source.target;
567
+ if (!local && source.spec.products) ctx.diag.error("local_only_section", "/spec/products", "products applies only under target device: local");
568
+ if (!local && source.precedence) ctx.diag.error("local_only_section", "/precedence", "precedence applies only under target device: local");
569
+ if (local && source.spec.members) ctx.diag.error("enterprise_only_section", "/spec/members", "members applies only to an enterprise target");
570
+ if (local && source.metadata.orgId) ctx.diag.error("enterprise_only_field", "/metadata/orgId", "a local policy carries no orgId");
571
+ }
572
+ function validateRules(ctx, rules, path) {
573
+ for (const [key, variants] of Object.entries(rules ?? {})) {
574
+ validateVariants(ctx, variants, `${path}/${key}`);
575
+ }
576
+ }
577
+ function validateVariants(ctx, variants, path) {
578
+ variants.forEach((variant, i) => {
579
+ const vpath = `${path}/${i}`;
580
+ if (i > 0 && isUnconditional(variants[i - 1])) {
581
+ ctx.diag.error("unreachable_variant", vpath, `variant ${i - 1} has no when, so this variant can never be reached`);
582
+ }
583
+ validateVariant(ctx, variant, vpath);
584
+ });
585
+ }
586
+ function isUnconditional(variant) {
587
+ return !variant.when || Object.keys(variant.when).length === 0;
588
+ }
589
+ function validateVariant(ctx, variant, path) {
590
+ if (variant.action === "hold") validateHoldApprover(ctx, variant, path);
591
+ for (const [field, spec] of Object.entries(variant.when ?? {})) validateCondition(ctx, spec, `${path}/when/${field}`);
592
+ }
593
+ function validateHoldApprover(ctx, variant, path) {
594
+ if (variant.approval) return void validateApproval(ctx, variant.approval, `${path}/approval`, true);
595
+ if (!ctx.approvalDefaults) {
596
+ ctx.diag.error("hold_without_approver", path, "a hold names an approval or the document sets approvalDefaults");
597
+ }
598
+ }
599
+ function validateApproval(ctx, approval, path, requireResolvable) {
600
+ if (!Number.isInteger(approval.approvers) || approval.approvers < 1) {
601
+ ctx.diag.error("approvers_count", `${path}/approvers`, "approvers is a whole number of at least 1");
602
+ }
603
+ if (requireResolvable && !namesAnApprover(approval)) {
604
+ ctx.diag.error("approval_unresolvable", path, "an approval names roles, members or anyMember: true");
605
+ }
606
+ }
607
+ function namesAnApprover(approval) {
608
+ return (approval.roles?.length ?? 0) > 0 || (approval.members?.length ?? 0) > 0 || approval.anyMember === true;
609
+ }
610
+ var OPERANDS = {
611
+ eq: [() => true, "any value"],
612
+ neq: [() => true, "any value"],
613
+ in: [Array.isArray, "a list"],
614
+ notIn: [Array.isArray, "a list"],
615
+ gt: [isNumber, "a number"],
616
+ gte: [isNumber, "a number"],
617
+ lt: [isNumber, "a number"],
618
+ lte: [isNumber, "a number"],
619
+ matches: [isPortablePattern, "a regular expression in the portable subset"],
620
+ notMatches: [isPortablePattern, "a regular expression in the portable subset"],
621
+ startsWith: [isString, "a string"],
622
+ glob: [isString, "a glob"],
623
+ withinWorkspace: [isBoolean, "true or false"],
624
+ exists: [isBoolean, "true or false"]
625
+ };
626
+ function validateCondition(ctx, spec, path) {
627
+ const [op, value] = Object.entries(spec)[0];
628
+ const [check, expected] = OPERANDS[op];
629
+ if (!check(value)) ctx.diag.error("condition_operand", `${path}/${op}`, `${op} takes ${expected}`);
630
+ }
631
+ function isNumber(v) {
632
+ return typeof v === "number" && Number.isFinite(v);
633
+ }
634
+ function isString(v) {
635
+ return typeof v === "string";
636
+ }
637
+ function isBoolean(v) {
638
+ return typeof v === "boolean";
639
+ }
640
+ function validateOverlays(ctx, overlays2, path) {
641
+ for (const [key, overlay] of Object.entries(overlays2 ?? {})) {
642
+ const opath = `${path}/${key}`;
643
+ validateRules(ctx, overlay.tools?.rules, `${opath}/tools/rules`);
644
+ flagLoosening(ctx, overlay, opath);
645
+ }
646
+ }
647
+ function flagLoosening(ctx, overlay, path) {
648
+ const baseDefault = ctx.base.default;
649
+ if (overlay.tools?.default && looser(overlay.tools.default, baseDefault)) {
650
+ ctx.diag.warning("loosening_grant", `${path}/tools/default`, `default ${overlay.tools.default} is looser than the base default ${baseDefault}`);
651
+ }
652
+ for (const [key, variants] of Object.entries(overlay.tools?.rules ?? {})) {
653
+ const base = baseVerdictFor(ctx.base, key);
654
+ const loosest = variants.reduce((v, x) => looser(x.action, v) ? x.action : v, "block");
655
+ if (looser(loosest, base)) {
656
+ ctx.diag.warning("loosening_grant", `${path}/tools/rules/${key}`, `${key}: ${loosest} is looser than the base ${base}`);
657
+ }
658
+ }
659
+ }
660
+ function baseVerdictFor(base, key) {
661
+ const variants = base.rules?.[key];
662
+ if (!variants) return base.default;
663
+ return variants.reduce((v, x) => looser(v, x.action) ? x.action : v, "allow");
664
+ }
665
+ function looser(a, b) {
666
+ return VERDICT_RANK[a] < VERDICT_RANK[b];
667
+ }
668
+ function validateCapture(ctx, source) {
669
+ const c = source.spec.capture;
670
+ if (!c) return;
671
+ if (c.sample !== void 0 && (c.sample < 0 || c.sample > 1)) ctx.diag.error("capture_sample", "/spec/capture/sample", "sample is between 0 and 1");
672
+ if (c.maxBytesPerCall !== void 0 && (c.maxBytesPerCall < 0 || !Number.isInteger(c.maxBytesPerCall))) {
673
+ ctx.diag.error("capture_max_bytes", "/spec/capture/maxBytesPerCall", "maxBytesPerCall is a whole number of bytes");
674
+ }
675
+ }
676
+
677
+ // src/compile/serialize.ts
678
+ var import_yaml2 = require("yaml");
679
+ function serialize(source) {
680
+ const spec = { ...source.spec, tools: { ...source.spec.tools, rules: shorthand(source.spec.tools.rules) } };
681
+ if (spec.members) spec.members = overlays(spec.members);
682
+ if (spec.products) spec.products = overlays(spec.products);
683
+ const doc = {
684
+ apiVersion: source.apiVersion,
685
+ kind: source.kind,
686
+ metadata: source.metadata,
687
+ target: source.target
688
+ };
689
+ if (source.precedence) doc.precedence = source.precedence;
690
+ doc.spec = spec;
691
+ return (0, import_yaml2.stringify)(doc, { lineWidth: 0, defaultStringType: "PLAIN", defaultKeyType: "PLAIN", aliasDuplicateObjects: false });
692
+ }
693
+ function shorthand(rules) {
694
+ if (!rules) return void 0;
695
+ const out = {};
696
+ for (const [key, value] of Object.entries(rules)) out[key] = scalarIfPlain(value);
697
+ return out;
698
+ }
699
+ function scalarIfPlain(variants) {
700
+ const only = variants.length === 1 ? variants[0] : null;
701
+ const plain = only && Object.keys(only).length === 2 && only.expiresAt === "never";
702
+ return plain ? only.action : variants;
703
+ }
704
+ function overlays(map) {
705
+ const out = {};
706
+ for (const [key, overlay] of Object.entries(map)) {
707
+ out[key] = overlay.tools?.rules ? { ...overlay, tools: { ...overlay.tools, rules: shorthand(overlay.tools.rules) } } : overlay;
708
+ }
709
+ return out;
710
+ }
711
+ // Annotate the CommonJS export names for ESM import in node:
712
+ 0 && (module.exports = {
713
+ Diagnostics,
714
+ NEVER,
715
+ SOURCE_OPERATORS,
716
+ VERDICTS,
717
+ parseDocument,
718
+ parseYaml,
719
+ serialize,
720
+ validate
721
+ });
722
+ //# sourceMappingURL=document.cjs.map