@highflame/policy 1.1.3 → 1.2.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.
Files changed (55) hide show
  1. package/dist/actions.gen.d.ts +21 -0
  2. package/dist/actions.gen.d.ts.map +1 -1
  3. package/dist/actions.gen.js +21 -0
  4. package/dist/actions.gen.js.map +1 -1
  5. package/dist/builder.d.ts +47 -10
  6. package/dist/builder.d.ts.map +1 -1
  7. package/dist/builder.js.map +1 -1
  8. package/dist/engine.d.ts +37 -0
  9. package/dist/engine.d.ts.map +1 -1
  10. package/dist/engine.js +99 -0
  11. package/dist/engine.js.map +1 -1
  12. package/dist/engine.test.d.ts +8 -0
  13. package/dist/engine.test.d.ts.map +1 -0
  14. package/dist/engine.test.js +190 -0
  15. package/dist/engine.test.js.map +1 -0
  16. package/dist/entities.gen.d.ts +4 -0
  17. package/dist/entities.gen.d.ts.map +1 -1
  18. package/dist/entities.gen.js +4 -0
  19. package/dist/entities.gen.js.map +1 -1
  20. package/dist/errors.d.ts +102 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/errors.js +127 -0
  23. package/dist/errors.js.map +1 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/parser.d.ts +34 -0
  29. package/dist/parser.d.ts.map +1 -0
  30. package/dist/parser.js +393 -0
  31. package/dist/parser.js.map +1 -0
  32. package/dist/parser.test.d.ts +8 -0
  33. package/dist/parser.test.d.ts.map +1 -0
  34. package/dist/parser.test.js +143 -0
  35. package/dist/parser.test.js.map +1 -0
  36. package/dist/schema.gen.d.ts +1 -1
  37. package/dist/schema.gen.d.ts.map +1 -1
  38. package/dist/schema.gen.js +331 -17
  39. package/dist/schema.gen.js.map +1 -1
  40. package/dist/types.d.ts +1 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js +2 -0
  43. package/dist/types.js.map +1 -1
  44. package/package.json +8 -2
  45. package/src/actions.gen.ts +21 -0
  46. package/src/builder.ts +52 -10
  47. package/src/engine.test.ts +371 -0
  48. package/src/engine.ts +145 -0
  49. package/src/entities.gen.ts +4 -0
  50. package/src/errors.ts +195 -0
  51. package/src/index.ts +2 -0
  52. package/src/parser.test.ts +169 -0
  53. package/src/parser.ts +517 -0
  54. package/src/schema.gen.ts +331 -17
  55. package/src/types.ts +3 -0
package/dist/parser.js ADDED
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Cedar Policy Parser
3
+ *
4
+ * Converts Cedar policy text to structured PolicyRule format using the
5
+ * official Cedar engine (cedar-wasm) for parsing.
6
+ *
7
+ * Architecture:
8
+ * 1. Cedar text → Cedar JSON (via cedar-wasm policyToJson)
9
+ * 2. Cedar JSON → PolicyRule (simple JSON mapping)
10
+ */
11
+ import * as cedar from "@cedar-policy/cedar-wasm/nodejs";
12
+ import { ParserError, ErrorCodes } from "./errors.js";
13
+ /**
14
+ * Normalize entity reference to simple { type, id } format
15
+ */
16
+ function normalizeEntityRef(ref) {
17
+ if ("__entity" in ref) {
18
+ return ref.__entity;
19
+ }
20
+ return ref;
21
+ }
22
+ /**
23
+ * Parse Cedar policy text and convert to PolicyRule format.
24
+ *
25
+ * Uses the official cedar-wasm engine for parsing, ensuring correctness.
26
+ * Policies with features that can't be represented as PolicyRule (e.g.,
27
+ * unless clauses, complex expressions) are returned in the unstructured array.
28
+ *
29
+ * @param cedarText - Cedar policy text to parse
30
+ * @returns ParseResult with structured rules, unstructured policies, and errors
31
+ */
32
+ export function parseCedarToRules(cedarText) {
33
+ const result = {
34
+ rules: [],
35
+ unstructured: [],
36
+ errors: [],
37
+ };
38
+ try {
39
+ // Split the policy set into individual policies and templates
40
+ const partsResult = cedar.policySetTextToParts(cedarText);
41
+ if (partsResult.type === "failure") {
42
+ for (const error of partsResult.errors) {
43
+ result.errors.push(error.message);
44
+ }
45
+ return result;
46
+ }
47
+ // Process each policy
48
+ let index = 0;
49
+ for (const policyText of partsResult.policies) {
50
+ // Convert individual policy to JSON using cedar-wasm
51
+ const jsonResult = cedar.policyToJson(policyText);
52
+ if (jsonResult.type === "failure") {
53
+ for (const error of jsonResult.errors) {
54
+ result.errors.push(`Policy ${index}: ${error.message}`);
55
+ }
56
+ index++;
57
+ continue;
58
+ }
59
+ const policy = jsonResult.json;
60
+ const policyId = policy.annotations?.id || `policy${index}`;
61
+ const conversion = cedarJsonToRule(policy, policyId, index, policyText);
62
+ if (conversion.error) {
63
+ result.errors.push(`Policy ${policyId}: ${conversion.error}`);
64
+ }
65
+ if (conversion.rule) {
66
+ result.rules.push(conversion.rule);
67
+ }
68
+ else if (conversion.raw) {
69
+ result.unstructured.push(conversion.raw);
70
+ }
71
+ index++;
72
+ }
73
+ // Templates can't be represented as PolicyRule
74
+ for (const templateText of partsResult.policy_templates) {
75
+ result.unstructured.push(templateText);
76
+ }
77
+ }
78
+ catch (e) {
79
+ result.errors.push(`Parse error: ${e instanceof Error ? e.message : String(e)}`);
80
+ }
81
+ // Check for duplicate policy IDs and add warnings
82
+ const idOccurrences = new Map();
83
+ result.rules.forEach((rule, idx) => {
84
+ if (rule.id) {
85
+ const indices = idOccurrences.get(rule.id) || [];
86
+ indices.push(idx);
87
+ idOccurrences.set(rule.id, indices);
88
+ }
89
+ });
90
+ for (const [id, indices] of idOccurrences) {
91
+ if (indices.length > 1) {
92
+ result.errors.push(`[${ErrorCodes.PARSE_DUPLICATE_ID}] Duplicate policy ID '${id}' found at indices [${indices.join(", ")}]`);
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+ /**
98
+ * Convert Cedar JSON policy to PolicyRule.
99
+ * This is pure JSON mapping - no parsing logic.
100
+ */
101
+ function cedarJsonToRule(policy, policyId, index, originalText) {
102
+ // Check if this policy can be represented as PolicyRule
103
+ if (!canRepresentAsRule(policy)) {
104
+ // Return original text if available, otherwise convert back from JSON
105
+ const raw = originalText || getRawCedar(policyId, policy);
106
+ return { raw };
107
+ }
108
+ try {
109
+ const rule = {
110
+ id: policy.annotations?.id || policyId,
111
+ name: policy.annotations?.name || policy.annotations?.id || policyId,
112
+ effect: policy.effect,
113
+ principal: mapScopeToEntity(policy.principal, "principal"),
114
+ action: mapActionScope(policy.action),
115
+ resource: mapScopeToEntity(policy.resource, "resource"),
116
+ conditions: [],
117
+ enabled: true,
118
+ order: index,
119
+ };
120
+ // Map description from annotations
121
+ if (policy.annotations?.description) {
122
+ rule.description = policy.annotations.description;
123
+ }
124
+ // Map conditions
125
+ const { conditions, rawCondition } = mapConditions(policy.conditions);
126
+ rule.conditions = conditions;
127
+ if (rawCondition) {
128
+ rule.rawCondition = rawCondition;
129
+ }
130
+ return { rule };
131
+ }
132
+ catch (e) {
133
+ const error = e instanceof Error ? e.message : String(e);
134
+ const raw = originalText || getRawCedar(policyId, policy);
135
+ return { raw, error };
136
+ }
137
+ }
138
+ /**
139
+ * Check if a Cedar policy can be represented as PolicyRule
140
+ */
141
+ function canRepresentAsRule(policy) {
142
+ // Unless clauses can't be represented
143
+ for (const cond of policy.conditions) {
144
+ if (cond.kind === "unless") {
145
+ return false;
146
+ }
147
+ }
148
+ // Template slots can't be represented
149
+ if (hasSlot(policy.principal) || hasSlot(policy.resource)) {
150
+ return false;
151
+ }
152
+ // Multiple entity "in" constraints are complex
153
+ const action = policy.action;
154
+ if (action.op === "in" && "entities" in action && action.entities.length > 1) {
155
+ // Multiple actions are OK, we handle those
156
+ }
157
+ return true;
158
+ }
159
+ /**
160
+ * Check if a scope constraint uses a slot (template)
161
+ */
162
+ function hasSlot(scope) {
163
+ if (scope.op === "All" || scope.op === "is") {
164
+ return false;
165
+ }
166
+ return "slot" in scope;
167
+ }
168
+ /**
169
+ * Get raw Cedar text for a policy that can't be represented as PolicyRule
170
+ */
171
+ function getRawCedar(policyId, policy) {
172
+ try {
173
+ // policyToText accepts Policy which is string | PolicyJson
174
+ const textResult = cedar.policyToText(policy);
175
+ if (textResult.type === "success") {
176
+ return textResult.text;
177
+ }
178
+ }
179
+ catch {
180
+ // Ignore conversion errors
181
+ }
182
+ // Sanitize policyId to prevent newline injection attacks
183
+ const safeId = policyId.replace(/[\n\r]/g, " ");
184
+ return `// Complex policy: ${safeId}`;
185
+ }
186
+ /**
187
+ * Map Cedar scope constraint to PolicyEntity
188
+ *
189
+ * Returns null for unconstrained ("All") scopes.
190
+ * Throws ParserError for malformed constraints to prevent silent misinterpretation.
191
+ *
192
+ * @param scope - The Cedar scope constraint
193
+ * @param field - The field name ("principal" or "resource") for error context
194
+ */
195
+ function mapScopeToEntity(scope, field) {
196
+ if (scope.op === "All") {
197
+ return null;
198
+ }
199
+ if (scope.op === "==") {
200
+ if ("entity" in scope) {
201
+ const entity = normalizeEntityRef(scope.entity);
202
+ return { type: entity.type, id: entity.id };
203
+ }
204
+ if ("slot" in scope) {
205
+ throw ParserError.scopeSlotNotSupported("==", field);
206
+ }
207
+ throw ParserError.scopeMissingEntity("==", field);
208
+ }
209
+ if (scope.op === "is") {
210
+ if (scope.entity_type) {
211
+ return { type: scope.entity_type };
212
+ }
213
+ throw ParserError.scopeMissingEntityType(field);
214
+ }
215
+ if (scope.op === "in") {
216
+ if ("entity" in scope) {
217
+ const entity = normalizeEntityRef(scope.entity);
218
+ return { type: entity.type, id: entity.id };
219
+ }
220
+ if ("slot" in scope) {
221
+ throw ParserError.scopeSlotNotSupported("in", field);
222
+ }
223
+ throw ParserError.scopeMissingEntityList(field);
224
+ }
225
+ throw ParserError.scopeUnsupportedOp(scope.op, field);
226
+ }
227
+ /**
228
+ * Map action scope to action string(s)
229
+ *
230
+ * Throws ParserError for malformed constraints to prevent silent misinterpretation.
231
+ */
232
+ function mapActionScope(scope) {
233
+ if (scope.op === "All") {
234
+ return "*";
235
+ }
236
+ if (scope.op === "==") {
237
+ if ("entity" in scope) {
238
+ const entity = normalizeEntityRef(scope.entity);
239
+ return entity.id;
240
+ }
241
+ throw ParserError.actionMissingEntity("==");
242
+ }
243
+ if (scope.op === "in") {
244
+ if ("entities" in scope) {
245
+ const actions = scope.entities.map(e => normalizeEntityRef(e).id);
246
+ return actions.length === 1 ? actions[0] : actions;
247
+ }
248
+ if ("entity" in scope) {
249
+ const entity = normalizeEntityRef(scope.entity);
250
+ return entity.id;
251
+ }
252
+ throw ParserError.actionMissingEntities();
253
+ }
254
+ throw ParserError.actionUnsupportedOp(scope.op);
255
+ }
256
+ /**
257
+ * Map Cedar conditions to PolicyCondition array
258
+ */
259
+ function mapConditions(conditions) {
260
+ const result = [];
261
+ const rawParts = [];
262
+ for (const cond of conditions) {
263
+ if (cond.kind !== "when") {
264
+ continue;
265
+ }
266
+ const parsed = mapConditionBody(cond.body);
267
+ if (parsed.condition) {
268
+ result.push(parsed.condition);
269
+ }
270
+ else if (parsed.raw) {
271
+ rawParts.push(parsed.raw);
272
+ }
273
+ }
274
+ // Store raw conditions as a valid JSON array instead of joining with " && "
275
+ // This ensures downstream systems can parse the rawCondition field
276
+ return {
277
+ conditions: result,
278
+ rawCondition: rawParts.length > 0 ? `[${rawParts.join(",")}]` : undefined,
279
+ };
280
+ }
281
+ /**
282
+ * Map a Cedar expression body to PolicyCondition
283
+ *
284
+ * Cedar JSON expressions use nested objects with operator keys.
285
+ * Comparison format: { "==": { left: { ".": { left: { Var: "context" }, attr: "field" } }, right: { Value: "x" } } }
286
+ */
287
+ function mapConditionBody(body) {
288
+ const expr = body;
289
+ // Check comparison operators
290
+ for (const op of ["==", "!=", "<", "<=", ">", ">="]) {
291
+ const comparison = expr[op];
292
+ if (comparison) {
293
+ const condition = mapComparison(op, comparison);
294
+ if (condition)
295
+ return { condition };
296
+ }
297
+ }
298
+ // Check contains
299
+ if (expr.contains) {
300
+ const condition = mapContains(expr.contains);
301
+ if (condition)
302
+ return { condition };
303
+ }
304
+ // Check like
305
+ if (expr.like) {
306
+ const condition = mapLike(expr.like);
307
+ if (condition)
308
+ return { condition };
309
+ }
310
+ // Can't map - return as raw JSON
311
+ return { raw: JSON.stringify(body) };
312
+ }
313
+ function mapComparison(op, args) {
314
+ const field = extractContextField(args.left);
315
+ if (!field)
316
+ return null;
317
+ const value = extractLiteralValue(args.right);
318
+ if (value === undefined)
319
+ return null;
320
+ const operator = mapOperator(op);
321
+ if (!operator)
322
+ return null;
323
+ return { field, operator, value };
324
+ }
325
+ function mapContains(args) {
326
+ const field = extractContextField(args.left);
327
+ if (!field)
328
+ return null;
329
+ const value = extractLiteralValue(args.right);
330
+ if (value === undefined)
331
+ return null;
332
+ return { field, operator: "contains", value };
333
+ }
334
+ function mapLike(args) {
335
+ const field = extractContextField(args.left);
336
+ if (!field)
337
+ return null;
338
+ // Convert pattern to string (e.g., ["Wildcard", { Literal: "foo" }, "Wildcard"] -> "*foo*")
339
+ // Escape literal * characters to distinguish from wildcards on round-trip
340
+ const patternStr = args.pattern.map(p => {
341
+ if (p === "Wildcard")
342
+ return "*";
343
+ if (typeof p === "object" && "Literal" in p)
344
+ return p.Literal.replace(/\*/g, "\\*");
345
+ return "";
346
+ }).join("");
347
+ return { field, operator: "like", value: patternStr };
348
+ }
349
+ /**
350
+ * Extract field name from context.field access pattern
351
+ * Pattern: { ".": { left: { Var: "context" }, attr: "field_name" } }
352
+ */
353
+ function extractContextField(expr) {
354
+ const dotAccess = expr["."];
355
+ if (!dotAccess)
356
+ return null;
357
+ // Check if accessing context variable
358
+ const leftExpr = dotAccess.left;
359
+ if (leftExpr.Var !== "context")
360
+ return null;
361
+ return dotAccess.attr;
362
+ }
363
+ /**
364
+ * Extract literal value from Cedar JSON
365
+ * Pattern: { Value: <literal> }
366
+ */
367
+ function extractLiteralValue(expr) {
368
+ if (!("Value" in expr))
369
+ return undefined;
370
+ const value = expr.Value;
371
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
372
+ return value;
373
+ }
374
+ if (Array.isArray(value) && value.every(v => typeof v === "string")) {
375
+ return value;
376
+ }
377
+ return undefined;
378
+ }
379
+ /**
380
+ * Map Cedar operator to ConditionOperator
381
+ */
382
+ function mapOperator(cedarOp) {
383
+ const mapping = {
384
+ "==": "eq",
385
+ "!=": "neq",
386
+ "<": "lt",
387
+ "<=": "lte",
388
+ ">": "gt",
389
+ ">=": "gte",
390
+ };
391
+ return mapping[cedarOp] || null;
392
+ }
393
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Parser unit tests
3
+ *
4
+ * Tests the Cedar text → PolicyRule conversion using the official Cedar engine.
5
+ * These tests demonstrate how a client like highflame-authz would use the parser.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.test.d.ts","sourceRoot":"","sources":["../src/parser.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Parser unit tests
3
+ *
4
+ * Tests the Cedar text → PolicyRule conversion using the official Cedar engine.
5
+ * These tests demonstrate how a client like highflame-authz would use the parser.
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+ import { parseCedarToRules } from './parser.js';
9
+ describe('parseCedarToRules', () => {
10
+ it('should parse a simple permit policy', () => {
11
+ const cedarText = `
12
+ @id("allow-read-files")
13
+ permit(
14
+ principal is User,
15
+ action == Action::"read_file",
16
+ resource is FilePath
17
+ );
18
+ `;
19
+ const result = parseCedarToRules(cedarText);
20
+ expect(result.errors).toHaveLength(0);
21
+ expect(result.rules).toHaveLength(1);
22
+ expect(result.unstructured).toHaveLength(0);
23
+ const rule = result.rules[0];
24
+ expect(rule.id).toBe('allow-read-files');
25
+ expect(rule.effect).toBe('permit');
26
+ expect(rule.principal).toEqual({ type: 'User' });
27
+ expect(rule.action).toBe('read_file');
28
+ expect(rule.resource).toEqual({ type: 'FilePath' });
29
+ expect(rule.enabled).toBe(true);
30
+ });
31
+ it('should parse a policy with when conditions', () => {
32
+ const cedarText = `
33
+ @id("block-high-risk")
34
+ forbid(
35
+ principal,
36
+ action == Action::"execute_tool",
37
+ resource
38
+ )
39
+ when {
40
+ context.threat_level == "high"
41
+ };
42
+ `;
43
+ const result = parseCedarToRules(cedarText);
44
+ expect(result.errors).toHaveLength(0);
45
+ expect(result.rules).toHaveLength(1);
46
+ const rule = result.rules[0];
47
+ expect(rule.id).toBe('block-high-risk');
48
+ expect(rule.effect).toBe('forbid');
49
+ expect(rule.action).toBe('execute_tool');
50
+ // Check condition was parsed
51
+ expect(rule.conditions.length).toBeGreaterThanOrEqual(0);
52
+ // If condition parsing works, it should have the condition
53
+ // If not, it should be in rawCondition
54
+ if (rule.conditions.length > 0) {
55
+ expect(rule.conditions[0].field).toBe('threat_level');
56
+ expect(rule.conditions[0].operator).toBe('eq');
57
+ expect(rule.conditions[0].value).toBe('high');
58
+ }
59
+ else {
60
+ expect(rule.rawCondition).toBeDefined();
61
+ }
62
+ });
63
+ it('should return errors for invalid Cedar syntax', () => {
64
+ const invalidCedar = `
65
+ permit(
66
+ principal is User
67
+ // missing comma and rest of policy
68
+ `;
69
+ const result = parseCedarToRules(invalidCedar);
70
+ expect(result.errors.length).toBeGreaterThan(0);
71
+ });
72
+ it('should handle multiple policies', () => {
73
+ const cedarText = `
74
+ @id("rule-1")
75
+ permit(principal, action == Action::"read", resource);
76
+
77
+ @id("rule-2")
78
+ forbid(principal, action == Action::"delete", resource);
79
+ `;
80
+ const result = parseCedarToRules(cedarText);
81
+ expect(result.errors).toHaveLength(0);
82
+ expect(result.rules).toHaveLength(2);
83
+ expect(result.rules[0].effect).toBe('permit');
84
+ expect(result.rules[1].effect).toBe('forbid');
85
+ });
86
+ it('should put policies with unless clauses in unstructured', () => {
87
+ const cedarText = `
88
+ permit(principal, action, resource)
89
+ unless {
90
+ context.is_blocked == true
91
+ };
92
+ `;
93
+ const result = parseCedarToRules(cedarText);
94
+ // Unless clauses can't be represented as PolicyRule
95
+ expect(result.rules).toHaveLength(0);
96
+ expect(result.unstructured.length).toBeGreaterThan(0);
97
+ });
98
+ it('should store complex conditions as valid JSON array in rawCondition', () => {
99
+ // Use a condition with boolean AND that can't be mapped to structured format
100
+ const cedarText = `
101
+ @id("complex-condition")
102
+ permit(principal, action, resource)
103
+ when {
104
+ context.a == "x" && context.b == "y"
105
+ };
106
+ `;
107
+ const result = parseCedarToRules(cedarText);
108
+ expect(result.errors).toHaveLength(0);
109
+ expect(result.rules).toHaveLength(1);
110
+ const rule = result.rules[0];
111
+ // The complex && condition should be in rawCondition as valid JSON array
112
+ if (rule.rawCondition) {
113
+ // Verify it's valid JSON (should not throw)
114
+ const parsed = JSON.parse(rule.rawCondition);
115
+ expect(Array.isArray(parsed)).toBe(true);
116
+ expect(parsed.length).toBeGreaterThan(0);
117
+ }
118
+ // Either conditions were mapped or rawCondition contains valid JSON
119
+ expect(rule.conditions.length > 0 || rule.rawCondition).toBeTruthy();
120
+ });
121
+ it('should warn about duplicate policy IDs', () => {
122
+ const cedarText = `
123
+ @id("duplicate-id")
124
+ permit(principal, action, resource);
125
+
126
+ @id("unique-id")
127
+ forbid(principal, action, resource);
128
+
129
+ @id("duplicate-id")
130
+ permit(principal is User, action, resource);
131
+ `;
132
+ const result = parseCedarToRules(cedarText);
133
+ // All policies should still be parsed
134
+ expect(result.rules).toHaveLength(3);
135
+ // Should have a warning about duplicate ID
136
+ const duplicateError = result.errors.find(e => e.includes("HFP-PARSE-005"));
137
+ expect(duplicateError).toBeDefined();
138
+ expect(duplicateError).toContain("duplicate-id");
139
+ expect(duplicateError).toContain("[0"); // First occurrence at index 0
140
+ expect(duplicateError).toContain("2"); // Second occurrence at index 2
141
+ });
142
+ });
143
+ //# sourceMappingURL=parser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.test.js","sourceRoot":"","sources":["../src/parser.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,SAAS,GAAG;;;;;;;KAOjB,CAAC;QAEF,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAE5C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAE5C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,SAAS,GAAG;;;;;;;;;;KAUjB,CAAC;QAEF,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAE5C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAErC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAEzC,6BAA6B;QAC7B,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACzD,2DAA2D;QAC3D,uCAAuC;QACvC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,YAAY,GAAG;;;;KAIpB,CAAC;QAEF,MAAM,MAAM,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;QAE/C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,SAAS,GAAG;;;;;;KAMjB,CAAC;QAEF,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAE5C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,SAAS,GAAG;;;;;KAKjB,CAAC;QAEF,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAE5C,oDAAoD;QACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,6EAA6E;QAC7E,MAAM,SAAS,GAAG;;;;;;KAMjB,CAAC;QAEF,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAE5C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAErC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAE7B,yEAAyE;QACzE,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,4CAA4C;YAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,oEAAoE;QACpE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,UAAU,EAAE,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,SAAS,GAAG;;;;;;;;;KASjB,CAAC;QAEF,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAE5C,sCAAsC;QACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAErC,2CAA2C;QAC3C,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;QAC5E,MAAM,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACjD,MAAM,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAE,8BAA8B;QACvE,MAAM,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAG,+BAA+B;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}