@nmnmcc/ability 2.0.7 → 2.1.2

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 (87) hide show
  1. package/README.md +52 -29
  2. package/dist/Authorizer.d.ts +21 -12
  3. package/dist/Authorizer.d.ts.map +1 -1
  4. package/dist/Authorizer.js.map +1 -1
  5. package/dist/Condition.d.ts +121 -19
  6. package/dist/Condition.d.ts.map +1 -1
  7. package/dist/Condition.js +32 -21
  8. package/dist/Condition.js.map +1 -1
  9. package/dist/Domain.d.ts +9 -0
  10. package/dist/Domain.d.ts.map +1 -1
  11. package/dist/Domain.js +5 -0
  12. package/dist/Domain.js.map +1 -1
  13. package/dist/Limits.d.ts +44 -13
  14. package/dist/Limits.d.ts.map +1 -1
  15. package/dist/Limits.js +52 -22
  16. package/dist/Limits.js.map +1 -1
  17. package/dist/Policy.d.ts +102 -18
  18. package/dist/Policy.d.ts.map +1 -1
  19. package/dist/Policy.js +7 -6
  20. package/dist/Policy.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/internal/algebra.js +2 -2
  23. package/dist/internal/analysis.js +81 -75
  24. package/dist/internal/analysis.js.map +1 -1
  25. package/dist/internal/authorizer.d.ts.map +1 -1
  26. package/dist/internal/authorizer.js +11 -7
  27. package/dist/internal/authorizer.js.map +1 -1
  28. package/dist/internal/bind.js +86 -64
  29. package/dist/internal/bind.js.map +1 -1
  30. package/dist/internal/catalog.d.ts +33 -10
  31. package/dist/internal/catalog.d.ts.map +1 -1
  32. package/dist/internal/catalog.js +170 -77
  33. package/dist/internal/catalog.js.map +1 -1
  34. package/dist/internal/decide.js +27 -31
  35. package/dist/internal/decide.js.map +1 -1
  36. package/dist/internal/domain.js +19 -5
  37. package/dist/internal/domain.js.map +1 -1
  38. package/dist/internal/json.js +120 -76
  39. package/dist/internal/json.js.map +1 -1
  40. package/dist/internal/limits.d.ts +28 -0
  41. package/dist/internal/limits.d.ts.map +1 -1
  42. package/dist/internal/limits.js +75 -16
  43. package/dist/internal/limits.js.map +1 -1
  44. package/dist/internal/nodes.d.ts.map +1 -1
  45. package/dist/internal/nodes.js +53 -44
  46. package/dist/internal/nodes.js.map +1 -1
  47. package/dist/internal/patterns.js +50 -15
  48. package/dist/internal/patterns.js.map +1 -1
  49. package/dist/internal/policy-json.js +169 -127
  50. package/dist/internal/policy-json.js.map +1 -1
  51. package/dist/internal/policy-nodes.d.ts.map +1 -1
  52. package/dist/internal/policy-nodes.js +16 -13
  53. package/dist/internal/policy-nodes.js.map +1 -1
  54. package/dist/internal/policy.d.ts.map +1 -1
  55. package/dist/internal/policy.js +38 -22
  56. package/dist/internal/policy.js.map +1 -1
  57. package/dist/internal/resource.js +1 -1
  58. package/dist/internal/rule.d.ts.map +1 -1
  59. package/dist/internal/rule.js +6 -5
  60. package/dist/internal/rule.js.map +1 -1
  61. package/dist/internal/search.d.ts.map +1 -1
  62. package/dist/internal/search.js +106 -92
  63. package/dist/internal/search.js.map +1 -1
  64. package/dist/internal/trampoline.js +19 -5
  65. package/dist/internal/trampoline.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/Authorizer.ts +30 -20
  68. package/src/Condition.ts +317 -86
  69. package/src/Domain.ts +9 -0
  70. package/src/Limits.ts +69 -29
  71. package/src/Policy.ts +143 -21
  72. package/src/internal/analysis.ts +154 -124
  73. package/src/internal/authorizer.ts +29 -14
  74. package/src/internal/bind.ts +193 -136
  75. package/src/internal/catalog.ts +320 -118
  76. package/src/internal/decide.ts +61 -50
  77. package/src/internal/domain.ts +39 -3
  78. package/src/internal/json.ts +212 -117
  79. package/src/internal/limits.ts +101 -31
  80. package/src/internal/nodes.ts +65 -47
  81. package/src/internal/patterns.ts +79 -25
  82. package/src/internal/policy-json.ts +266 -197
  83. package/src/internal/policy-nodes.ts +31 -23
  84. package/src/internal/policy.ts +63 -23
  85. package/src/internal/rule.ts +16 -3
  86. package/src/internal/search.ts +186 -136
  87. package/src/internal/trampoline.ts +51 -4
@@ -1,12 +1,12 @@
1
- import { searchSteps } from "./limits.js";
1
+ import { adaptive, run } from "./trampoline.js";
2
2
  import { isNode } from "./nodes.js";
3
3
  import { accepts, anyKinds, lookup } from "./catalog.js";
4
- import { fitsCatalog, parse } from "./patterns.js";
5
4
  import { at, pathOf } from "./place.js";
5
+ import { fitsCatalog, parse } from "./patterns.js";
6
6
  import { withSuggestion } from "./suggest.js";
7
+ import * as Arr from "effect/Array";
7
8
  import * as HashMap from "effect/HashMap";
8
9
  import * as Result from "effect/Result";
9
- import * as Arr from "effect/Array";
10
10
  import * as Option from "effect/Option";
11
11
  // src/internal/bind.ts
12
12
  /**
@@ -16,7 +16,7 @@ import * as Option from "effect/Option";
16
16
  * each problem, with the path of the problem and, for a name with a mistake,
17
17
  * the name that it may mean.
18
18
  */
19
- const makeMemo = () => ({
19
+ const makeMemo = (limits) => ({
20
20
  suggestions: /* @__PURE__ */ new Map(),
21
21
  fits: /* @__PURE__ */ new Map(),
22
22
  issues: {
@@ -26,7 +26,8 @@ const makeMemo = () => ({
26
26
  work: {
27
27
  steps: 0,
28
28
  noted: false
29
- }
29
+ },
30
+ limits
30
31
  });
31
32
  /**
32
33
  * The most names that one binding makes a suggestion for. A suggestion
@@ -67,12 +68,17 @@ const orderedMeet = (left, right) => hasStrings(left) && hasStrings(right) || ha
67
68
  const namesOf = (catalog) => Arr.map(Arr.filter(catalog.fields, (field) => Arr.every(field.path, (segment) => typeof segment === "string")), (field) => field.text);
68
69
  /** The issue of a field that is not a field of every resource of the rule. */
69
70
  const fieldIssue = (context, field, at) => {
71
+ const parts = context.memo.limits.fieldPathParts;
72
+ if (field.split(".", parts + 1).length > parts) return [{
73
+ path: at,
74
+ issue: `The field path "${field}" has more than ${String(parts)} parts. The most is ${String(parts)}.`
75
+ }];
70
76
  const message = `The field "${field}" is not a field of every resource of the rule`;
71
77
  if (context.all || context.named.length === 0) return [{
72
78
  path: at,
73
79
  issue: message
74
80
  }];
75
- const without = Arr.filter(context.named, (descriptor) => lookup(descriptor.catalog, field) === void 0);
81
+ const without = Arr.filter(context.named, (descriptor) => lookup(descriptor.catalog, field, parts) === void 0);
76
82
  return without.length === 0 ? [] : [{
77
83
  path: at,
78
84
  issue: suggestIn(context.memo, message, field, without[0].kind, () => namesOf(without[0].catalog))
@@ -89,7 +95,7 @@ const fieldIssue = (context, field, at) => {
89
95
  const readIssues = (context, field, at) => {
90
96
  const exists = fieldIssue(context, field, at);
91
97
  if (exists.length > 0) return exists;
92
- return Arr.some(context.named, (descriptor) => !(lookup(descriptor.catalog, field)?.plain ?? true)) ? [{
98
+ return Arr.some(context.named, (descriptor) => !(lookup(descriptor.catalog, field, context.memo.limits.fieldPathParts)?.plain ?? true)) ? [{
93
99
  path: at,
94
100
  issue: `A condition may not read the field "${field}". A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`
95
101
  }] : [];
@@ -99,7 +105,7 @@ const readIssues = (context, field, at) => {
99
105
  * matches no field of the resources of the rule, as a pattern with a mistake
100
106
  * in a name does.
101
107
  */
102
- const patternIssues = (memo, known, pattern, at) => Result.match(parse(pattern), {
108
+ const patternIssues = (memo, known, pattern, at) => Result.match(parse(pattern, memo.limits.patternParts), {
103
109
  onFailure: (issue) => [{
104
110
  path: at,
105
111
  issue
@@ -108,13 +114,13 @@ const patternIssues = (memo, known, pattern, at) => Result.match(parse(pattern),
108
114
  return known.length === 0 || Arr.reduce(known, false, (before, descriptor) => {
109
115
  const key = `${descriptor.kind}\u0000${pattern}`;
110
116
  return Option.getOrElse(Option.fromUndefinedOr(memo.fits.get(key)), () => {
111
- memo.work.steps += descriptor.catalog.fields.length * Math.max(parsed.length, 1);
112
- if (memo.work.steps > 1e6) return true;
113
- const found = fitsCatalog(parsed, descriptor.catalog);
117
+ memo.work.steps += Math.max(descriptor.catalog.edges, 1) * Math.max(parsed.length, 1);
118
+ if (memo.work.steps > memo.limits.searchSteps) return true;
119
+ const found = fitsCatalog(parsed, descriptor.catalog, memo.limits.fieldPathParts);
114
120
  memo.fits.set(key, found);
115
121
  return found;
116
122
  }) || before;
117
- }) || memo.work.steps > 1e6 ? [] : [{
123
+ }) || memo.work.steps > memo.limits.searchSteps ? [] : [{
118
124
  path: at,
119
125
  issue: `The field pattern "${pattern}" matches no field of the resources of the rule`
120
126
  }];
@@ -122,12 +128,12 @@ const patternIssues = (memo, known, pattern, at) => Result.match(parse(pattern),
122
128
  });
123
129
  /** The fields of the resources that a test of the rule may read. */
124
130
  const fieldsAt = (context, field) => Arr.flatMap(context.applicable, (descriptor) => {
125
- const found = lookup(descriptor.catalog, field);
131
+ const found = lookup(descriptor.catalog, field, context.memo.limits.fieldPathParts);
126
132
  return found === void 0 ? [] : [found];
127
133
  });
128
134
  /** The field of the principal that an operand names, or the issue when there is none. */
129
135
  const principalField = (context, ref, at) => {
130
- const found = lookup(context.domain.principalCatalog, ref);
136
+ const found = lookup(context.domain.principalCatalog, ref, context.memo.limits.fieldPathParts);
131
137
  if (found !== void 0 && !found.plain) return {
132
138
  path: at,
133
139
  issue: `A condition may not read the field "${ref}" of the principal. A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`
@@ -153,7 +159,7 @@ const distinctFields = (context, field) => Array.from(new Map(Arr.map(fieldsAt(c
153
159
  */
154
160
  const spendChecks = (memo, values, fields) => {
155
161
  memo.work.steps += values * Math.max(fields, 1);
156
- return memo.work.steps <= searchSteps;
162
+ return memo.work.steps <= memo.limits.searchSteps;
157
163
  };
158
164
  /** The issues of one operand of a test of a field, with its distinct fields. */
159
165
  const operandIssues = (context, field, fields, ordered, operand, at) => {
@@ -241,7 +247,7 @@ const under = (memo, at, issues) => {
241
247
  const room = maxIssues - memo.issues.count;
242
248
  memo.issues.count += issues.length;
243
249
  const kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({
244
- path: [...at(), ...issue.path],
250
+ path: [...pathOf(at), ...issue.path],
245
251
  issue: issue.issue
246
252
  }));
247
253
  const first = memo.issues.count > maxIssues && !memo.issues.noted;
@@ -258,46 +264,50 @@ const under = (memo, at, issues) => {
258
264
  * it has seen are in a native `Set`, because it must find a node by the node
259
265
  * itself: `Equal` compares nodes by structure.
260
266
  */
261
- const conditionIssues = (context, node, at, seen) => {
262
- if (seen.has(node)) return [];
263
- seen.add(node);
264
- const here = (issues) => under(context.memo, at, issues);
267
+ const conditionIssues = (context, root, rootAt, seen) => run(root.depth, (R) => {
265
268
  const room = (values) => spendChecks(context.memo, 1 + values, context.named.length);
266
- switch (node._tag) {
267
- case "Compare": {
268
- if (!room(1)) return [];
269
- const exists = readIssues(context, node.field, ["field"]);
270
- return here(exists.length > 0 ? exists : operandIssues(context, node.field, distinctFields(context, node.field), node.op !== "eq" && node.op !== "ne", node.value, ["value"]));
269
+ const leaf = (node, at) => {
270
+ const here = (issues) => under(context.memo, at, issues);
271
+ switch (node._tag) {
272
+ case "Compare": {
273
+ if (!room(1)) return [];
274
+ const exists = readIssues(context, node.field, ["field"]);
275
+ return here(exists.length > 0 ? exists : operandIssues(context, node.field, distinctFields(context, node.field), node.op !== "eq" && node.op !== "ne", node.value, ["value"]));
276
+ }
277
+ case "In": {
278
+ if (!room(0)) return [];
279
+ const exists = readIssues(context, node.field, ["field"]);
280
+ if (exists.length > 0) return here(exists);
281
+ const fields = distinctFields(context, node.field);
282
+ if (!spendChecks(context.memo, node.values.length, fields.length)) return [];
283
+ return here(Arr.flatMap(node.values, (operand, index) => operandIssues(context, node.field, fields, false, operand, ["values", index])));
284
+ }
285
+ case "Exists": return room(0) ? here(readIssues(context, node.field, ["field"])) : [];
286
+ case "Under": return here(underIssues(context, node.prefix, []));
287
+ default: return [];
271
288
  }
272
- case "In": {
273
- if (!room(0)) return [];
274
- const exists = readIssues(context, node.field, ["field"]);
275
- if (exists.length > 0) return here(exists);
276
- const fields = distinctFields(context, node.field);
277
- if (!spendChecks(context.memo, node.values.length, fields.length)) return [];
278
- return here(Arr.flatMap(node.values, (operand, index) => operandIssues(context, node.field, fields, false, operand, ["values", index])));
289
+ };
290
+ const go = (node, at$1) => {
291
+ if (seen.has(node)) return R.succeed([]);
292
+ seen.add(node);
293
+ switch (node._tag) {
294
+ case "And":
295
+ case "Or": return R.map(R.forEach(node.conditions, (child, index) => R.suspend(() => go(child, at(at$1, "conditions", index)))), Arr.flatten);
296
+ case "Not": return R.suspend(() => go(node.condition, at(at$1, "condition")));
297
+ default: return R.succeed(leaf(node, at$1));
279
298
  }
280
- case "Exists": return room(0) ? here(readIssues(context, node.field, ["field"])) : [];
281
- case "Under": return here(underIssues(context, node.prefix, []));
282
- case "And":
283
- case "Or": return Arr.flatMap(node.conditions, (child, index) => conditionIssues(context, child, () => [
284
- ...at(),
285
- "conditions",
286
- index
287
- ], seen));
288
- case "Not": return conditionIssues(context, node.condition, () => [...at(), "condition"], seen);
289
- default: return [];
290
- }
291
- };
299
+ };
300
+ return go(root, rootAt);
301
+ });
292
302
  const hasAction = (descriptor, actions) => actions.includes("*") || Arr.some(actions, (action) => descriptor.actions.includes(action));
293
303
  /**
294
304
  * Checks one rule against a domain. It gives one issue for each problem, up
295
- * to the most issues of the binding. `at` gives the path of the rule, and it
296
- * is made only for an issue that the binding keeps: a rule with very many
297
- * problems deep in a tree does not make a full path for each one.
305
+ * to the most issues of the binding. `at` is the place of the rule, and its
306
+ * full path is made only for an issue that the binding keeps: a rule with
307
+ * very many problems deep in a tree does not make a full path for each one.
298
308
  */
299
- const ruleIssues = (domain, rule, at, memo = makeMemo()) => {
300
- const here = (issues) => under(memo, at, issues);
309
+ const ruleIssues = (domain, rule, at$3, memo = makeMemo(domain.limits)) => {
310
+ const here = (issues) => under(memo, at$3, issues);
301
311
  const all = rule.resources.includes("*");
302
312
  const names = Array.from(new Set(Arr.filter(rule.resources, (kind) => kind !== "*")));
303
313
  const kinds = Array.from(HashMap.keys(domain.resources));
@@ -314,8 +324,12 @@ const ruleIssues = (domain, rule, at, memo = makeMemo()) => {
314
324
  };
315
325
  const actions = Array.from(new Set(Arr.flatMap(known, (descriptor) => descriptor.actions)));
316
326
  const fieldIssues = Arr.flatMap(rule.fields ?? [], (field, index) => here(field.includes("*") ? patternIssues(memo, known, field, ["fields", index]) : fieldIssue(context, field, ["fields", index])));
317
- const whenIssues = rule.when === void 0 ? [] : isNode(rule.when) ? conditionIssues(context, rule.when, () => [...at(), "when"], /* @__PURE__ */ new Set()) : here([notWhole(["when"])]);
318
- const work = memo.work.steps > 1e6 && !memo.work.noted;
327
+ const conditionNodes = memo.limits.conditionNodes;
328
+ const whenIssues = rule.when === void 0 ? [] : !isNode(rule.when) ? here([notWhole(["when"])]) : rule.when.weight > conditionNodes ? here([{
329
+ path: ["when"],
330
+ issue: `A condition has ${String(rule.when.weight)} nodes and values. The most is ${String(conditionNodes)}.`
331
+ }]) : conditionIssues(context, rule.when, at(at$3, "when"), /* @__PURE__ */ new Set());
332
+ const work = memo.work.steps > memo.limits.searchSteps && !memo.work.noted;
319
333
  memo.work.noted ||= work;
320
334
  return [
321
335
  ...Arr.flatMap(Arr.filter(names, (kind) => !HashMap.has(domain.resources, kind)), (kind) => here([{
@@ -329,28 +343,36 @@ const ruleIssues = (domain, rule, at, memo = makeMemo()) => {
329
343
  ...fieldIssues,
330
344
  ...whenIssues,
331
345
  ...work ? [{
332
- path: at(),
333
- issue: `The policy takes more than ${String(searchSteps)} steps to check: its field patterns, and the fields and values of its tests on each resource. The most is ${String(searchSteps)}.`
346
+ path: pathOf(at$3),
347
+ issue: `The policy takes more than ${String(memo.limits.searchSteps)} steps to check: its field patterns, and the fields and values of its tests on each resource. The most is ${String(memo.limits.searchSteps)}.`
334
348
  }] : []
335
349
  ];
336
350
  };
337
351
  /** Checks each rule of a policy tree against a domain. */
338
352
  const treeIssues = (domain, root) => {
339
- const rulesOf = (node, place, level) => {
340
- if (level > 256) return [{
341
- path: pathOf(place),
342
- issue: `A policy tree has more than ${String(256)} levels. The most is ${String(256)}.`
343
- }];
353
+ const rulesOf = adaptive((R, child) => ({ node, at: at$2 }, level) => {
344
354
  switch (node._tag) {
345
- case "Rules": return Arr.map(node.rules, (rule, index) => [rule, at(place, "rules", index)]);
355
+ case "Rules": return R.succeed(Arr.map(node.rules, (rule, index) => [rule, at(at$2, "rules", index)]));
346
356
  case "All":
347
357
  case "Any":
348
- case "FirstOf": return Arr.flatMap(node.policies, (child, index) => rulesOf(child, at(place, "policies", index), level + 1));
349
- case "Ceiling": return [...rulesOf(node.cap, at(place, "cap"), level + 1), ...rulesOf(node.policy, at(place, "policy"), level + 1)];
358
+ case "FirstOf": return R.map(R.forEach(node.policies, (inner, index) => child({
359
+ node: inner,
360
+ at: at(at$2, "policies", index)
361
+ }, level + 1)), Arr.flatten);
362
+ case "Ceiling": return R.map(R.forEach([{
363
+ node: node.cap,
364
+ at: at(at$2, "cap")
365
+ }, {
366
+ node: node.policy,
367
+ at: at(at$2, "policy")
368
+ }], (part) => child(part, level + 1)), Arr.flatten);
350
369
  }
351
- };
352
- const memo = makeMemo();
353
- return Arr.flatMap(rulesOf(root, void 0, 1), (found) => Array.isArray(found) ? ruleIssues(domain, found[0], () => pathOf(found[1]), memo) : [found]);
370
+ });
371
+ const memo = makeMemo(domain.limits);
372
+ return Arr.flatMap(rulesOf({
373
+ node: root,
374
+ at: void 0
375
+ }), (found) => Array.isArray(found) ? ruleIssues(domain, found[0], found[1], memo) : [found]);
354
376
  };
355
377
  export { ruleIssues, treeIssues };
356
378
 
@@ -1 +1 @@
1
- {"version":3,"file":"bind.js","names":["Catalog.accepts","Catalog.lookup","Patterns.parse","Patterns.fitsCatalog","Limits.searchSteps","Catalog.anyKinds","Nodes.isNode","Place.pathOf","Place.at"],"sources":["../../src/internal/bind.ts"],"sourcesContent":["/**\n * The binding of rules to a domain. It is the one check of a rule, for a rule\n * from the builder, from `Policy.rule`, and from JSON alike. It reads the\n * catalogs of the resources and of the principal, and it gives one issue for\n * each problem, with the path of the problem and, for a name with a mistake,\n * the name that it may mean.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Patterns from \"./patterns.ts\"\nimport * as Place from \"./place.ts\"\nimport { withSuggestion } from \"./suggest.ts\"\n\n/** One problem of a rule or a policy, with its path. */\nexport interface Issue {\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly issue: string\n}\n\n/** The data of a rule that the binding reads. */\nexport interface RuleData {\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: unknown\n\treadonly fields?: ReadonlyArray<string>\n}\n\n/**\n * The memo of one binding, for all its rules. A policy may name one wrong\n * field or one pattern in very many places, so the work on each is done one\n * time. It goes away when the binding ends.\n */\ninterface Memo {\n\t/**\n\t * The suggestion for each name, by the place where it was looked for. The\n\t * names come from a policy, so the map is native, as `fits` is.\n\t */\n\treadonly suggestions: Map<string, string>\n\t/**\n\t * If a pattern matches a field of a kind, by the kind and the pattern. The\n\t * patterns come from a policy, so the map is native: the hash of a string\n\t * in Effect has no random seed, and a policy could hold very many\n\t * patterns with one hash. The engine hashes a string with a random seed.\n\t */\n\treadonly fits: Map<string, boolean>\n\t/** The count of the issues of conditions so far. It belongs to one binding. */\n\treadonly issues: { count: number; noted: boolean }\n\t/** The steps of the checks of patterns so far. It belongs to one binding. */\n\treadonly work: { steps: number; noted: boolean }\n}\n\nconst makeMemo = (): Memo => ({\n\tsuggestions: new Map(),\n\tfits: new Map(),\n\tissues: { count: 0, noted: false },\n\twork: { steps: 0, noted: false },\n})\n\n/**\n * The most names that one binding makes a suggestion for. A suggestion\n * compares the name with every name of a catalog, so a policy with very many\n * wrong names would take a long time.\n */\nconst maxSuggestions = 32\n\n/** Adds a suggestion to a message, with the memo of the binding. */\nconst suggestIn = (\n\tmemo: Memo,\n\tmessage: string,\n\tname: string,\n\tscope: string,\n\tnames: () => ReadonlyArray<string>,\n): string => {\n\tconst key = `${scope}\\u0000${name}`\n\tconst known = memo.suggestions.get(key)\n\tif (known !== undefined) {\n\t\treturn known\n\t}\n\tif (memo.suggestions.size >= maxSuggestions) {\n\t\treturn message\n\t}\n\tconst made = withSuggestion(message, name, names())\n\tmemo.suggestions.set(key, made)\n\treturn made\n}\n\n/** The kinds that a rule may run on, from the view of its checks. */\ninterface Context {\n\treadonly domain: Domain.Any\n\treadonly memo: Memo\n\t/** The rule is for every resource (`\"*\"`). */\n\treadonly all: boolean\n\t/** The resources that the rule names and that the domain has. */\n\treadonly named: ReadonlyArray<Resource.Descriptor>\n\t/** The named resources that have one of the actions of the rule. */\n\treadonly applicable: ReadonlyArray<Resource.Descriptor>\n\t/**\n\t * The resources whose paths a path test may read: every resource with one\n\t * of the actions, for a rule for every resource.\n\t */\n\treadonly paths: ReadonlyArray<Resource.Descriptor>\n}\n\ntype Operand = Nodes.Operand\n\n/**\n * Tests if a field may hold a value of the type of a fixed value. A test with\n * `null` is a test that the field is missing, so `null` always fits. A number\n * fits any field of numbers: a test of an integer field with `1.5` or\n * `Infinity` has a meaning, as `lt` shows.\n */\nconst fitsLiteral = (kinds: Catalog.Kinds, value: unknown): boolean =>\n\tvalue === null ||\n\t(typeof value === \"number\" && kinds.number !== \"none\") ||\n\tCatalog.accepts(kinds, value)\n\n/**\n * Tests if two fields may hold one scalar. A principal field that is `null`\n * or that holds an object is a missing value, and a test with a missing value\n * is false, so only scalars count.\n */\nconst meet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(left.string && right.string) ||\n\t(left.boolean && right.boolean) ||\n\t(left.number !== \"none\" && right.number !== \"none\") ||\n\tArr.some(left.literals, (value) => Catalog.accepts(right, value)) ||\n\tArr.some(right.literals, (value) => Catalog.accepts(left, value))\n\nconst hasStrings = (kinds: Catalog.Kinds): boolean =>\n\tkinds.string || Arr.some(kinds.literals, (value) => typeof value === \"string\")\n\nconst hasNumbers = (kinds: Catalog.Kinds): boolean =>\n\tkinds.number !== \"none\" ||\n\tArr.some(kinds.literals, (value) => typeof value === \"number\")\n\n/** Tests if a field may hold a value with an order: a number or a string. */\nconst isOrdered = (kinds: Catalog.Kinds): boolean =>\n\thasStrings(kinds) || hasNumbers(kinds)\n\n/** Tests if two fields may hold values of one ordered type. */\nconst orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(hasStrings(left) && hasStrings(right)) ||\n\t(hasNumbers(left) && hasNumbers(right))\n\n/** The texts of the fields of a catalog that have only names, for suggestions. */\nconst namesOf = (catalog: Catalog.Catalog): ReadonlyArray<string> =>\n\tArr.map(\n\t\tArr.filter(catalog.fields, (field) =>\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t\t),\n\t\t(field) => field.text,\n\t)\n\n/** The issue of a field that is not a field of every resource of the rule. */\nconst fieldIssue = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst message = `The field \"${field}\" is not a field of every resource of the rule`\n\tif (context.all || context.named.length === 0) {\n\t\treturn [{ path: at, issue: message }]\n\t}\n\tconst without = Arr.filter(\n\t\tcontext.named,\n\t\t(descriptor) => Catalog.lookup(descriptor.catalog, field) === undefined,\n\t)\n\treturn without.length === 0\n\t\t? []\n\t\t: [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: suggestIn(context.memo, message, field, without[0]!.kind, () =>\n\t\t\t\t\t\tnamesOf(without[0]!.catalog),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t]\n}\n\n/**\n * The issues of a field that a condition reads: a field that is not a field\n * of every resource of the rule, or a field that is not plain data in one of\n * them. A read of a value that is not plain data, as a getter of a class, is\n * a defect in a decision. A residual may leave the read out, so the residual\n * would say \"yes\" where the decision fails. So the binding does not take\n * such a read: see `Catalog.Field.plain`.\n */\nconst readIssues = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst exists = fieldIssue(context, field, at)\n\tif (exists.length > 0) {\n\t\treturn exists\n\t}\n\treturn Arr.some(\n\t\tcontext.named,\n\t\t(descriptor) => !(Catalog.lookup(descriptor.catalog, field)?.plain ?? true),\n\t)\n\t\t? [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `A condition may not read the field \"${field}\". A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: []\n}\n\n/**\n * The issues of a field pattern: a pattern that does not parse, or that\n * matches no field of the resources of the rule, as a pattern with a mistake\n * in a name does.\n */\nconst patternIssues = (\n\tmemo: Memo,\n\tknown: ReadonlyArray<Resource.Descriptor>,\n\tpattern: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> =>\n\tResult.match(Patterns.parse(pattern), {\n\t\tonFailure: (issue) => [{ path: at, issue }],\n\t\tonSuccess: (parsed) => {\n\t\t\t// A rule with no known resource has an issue for that already. The\n\t\t\t// pattern is checked on every kind, not only up to the first that it\n\t\t\t// fits: a decision on a kind does this check, so the limit of the\n\t\t\t// work below is a limit of the work of a decision too.\n\t\t\tconst fits =\n\t\t\t\tknown.length === 0 ||\n\t\t\t\tArr.reduce(known, false, (before, descriptor) => {\n\t\t\t\t\tconst key = `${descriptor.kind}\\u0000${pattern}`\n\t\t\t\t\treturn (\n\t\t\t\t\t\tOption.getOrElse(Option.fromUndefinedOr(memo.fits.get(key)), () => {\n\t\t\t\t\t\t\t// Each check of a pattern reads the fields of a catalog. The\n\t\t\t\t\t\t\t// work of one binding has a limit, so very many patterns stop\n\t\t\t\t\t\t\t// with one issue, and the policy does not load.\n\t\t\t\t\t\t\tmemo.work.steps +=\n\t\t\t\t\t\t\t\tdescriptor.catalog.fields.length * Math.max(parsed.length, 1)\n\t\t\t\t\t\t\tif (memo.work.steps > Limits.searchSteps) {\n\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst found = Patterns.fitsCatalog(parsed, descriptor.catalog)\n\t\t\t\t\t\t\tmemo.fits.set(key, found)\n\t\t\t\t\t\t\treturn found\n\t\t\t\t\t\t}) || before\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t// Past the limit of the work, `ruleIssues` gives one issue for it.\n\t\t\treturn fits || memo.work.steps > Limits.searchSteps\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field pattern \"${pattern}\" matches no field of the resources of the rule`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t},\n\t})\n\n/** The fields of the resources that a test of the rule may read. */\nconst fieldsAt = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArr.flatMap(context.applicable, (descriptor) => {\n\t\tconst found = Catalog.lookup(descriptor.catalog, field)\n\t\treturn found === undefined ? [] : [found]\n\t})\n\n/** The field of the principal that an operand names, or the issue when there is none. */\nconst principalField = (\n\tcontext: Context,\n\tref: string,\n\tat: ReadonlyArray<PropertyKey>,\n): Catalog.Field | Issue => {\n\tconst found = Catalog.lookup(context.domain.principalCatalog, ref)\n\t// A read of the principal that is not plain data is a defect, as a read\n\t// of a resource is: see `readIssues`.\n\tif (found !== undefined && !found.plain) {\n\t\treturn {\n\t\t\tpath: at,\n\t\t\tissue: `A condition may not read the field \"${ref}\" of the principal. A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t}\n\t}\n\treturn found !== undefined\n\t\t? found\n\t\t: {\n\t\t\t\tpath: at,\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tcontext.memo,\n\t\t\t\t\t`The principal has no field \"${ref}\"`,\n\t\t\t\t\tref,\n\t\t\t\t\t\"~principal\",\n\t\t\t\t\t() => namesOf(context.domain.principalCatalog),\n\t\t\t\t),\n\t\t\t}\n}\n\nconst isIssue = (value: Catalog.Field | Issue): value is Issue =>\n\t\"issue\" in value\n\n/**\n * The fields that a test reads, with one field for each distinct kind of\n * value. A rule for very many kinds may have one field type in all of them,\n * so each value of the test is checked one time for each distinct kind, not\n * one time for each resource. The kinds are found by their text in a native\n * `Map`, which belongs to this call.\n */\nconst distinctFields = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArray.from(\n\t\tnew Map(\n\t\t\tArr.map(\n\t\t\t\tfieldsAt(context, field),\n\t\t\t\t(found) => [JSON.stringify(found.kinds), found] as const,\n\t\t\t),\n\t\t).values(),\n\t)\n\n/**\n * Counts the work of the checks of the values of a test, as the work of the\n * field patterns counts. Past the limit, the values are not checked, and\n * `ruleIssues` gives one issue for the work.\n */\nconst spendChecks = (memo: Memo, values: number, fields: number): boolean => {\n\tmemo.work.steps += values * Math.max(fields, 1)\n\treturn memo.work.steps <= Limits.searchSteps\n}\n\n/** The issues of one operand of a test of a field, with its distinct fields. */\nconst operandIssues = (\n\tcontext: Context,\n\tfield: string,\n\tfields: ReadonlyArray<Catalog.Field>,\n\tordered: boolean,\n\toperand: Operand,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tif (operand._tag === \"Literal\") {\n\t\tconst value = operand.value\n\t\tif (ordered && typeof value !== \"string\" && typeof value !== \"number\") {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t...(Arr.some(fields, (found) => !fitsLiteral(found.kinds, value))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" can not hold the value ${JSON.stringify(value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t\t...(ordered && Arr.some(fields, (found) => !isOrdered(found.kinds))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t]\n\t}\n\tconst principal = principalField(context, operand.path, at)\n\tif (isIssue(principal)) {\n\t\treturn [principal]\n\t}\n\treturn [\n\t\t...(Arr.some(fields, (found) => !meet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" can not hold a value of the principal field \"${operand.path}\"`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(ordered &&\n\t\tArr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/**\n * Tests if the path of a resource may start with a prefix: it is as long as\n * the prefix, and each fixed string of the prefix is the string of the path\n * at its place, when the path has a string there.\n */\nconst mayStartWith = (\n\tdescriptor: Resource.Descriptor,\n\tstrings: ReadonlyArray<string | null>,\n): boolean =>\n\t(descriptor.path?.length ?? 0) >= strings.length &&\n\tArr.every(strings, (string, index) => {\n\t\tconst segment = descriptor.path?.[index]\n\t\treturn string === null || typeof segment !== \"string\" || segment === string\n\t})\n\n/** The kinds of the part of the path of a resource at a place. */\nconst pathPartKinds = (\n\tdescriptor: Resource.Descriptor,\n\tindex: number,\n): Catalog.Kinds | string | undefined => {\n\tconst segment = descriptor.path?.[index]\n\treturn segment === undefined || typeof segment === \"string\"\n\t\t? segment\n\t\t: (Catalog.lookup(descriptor.catalog, segment.field)?.kinds ??\n\t\t\t\tCatalog.anyKinds)\n}\n\n/** The issues of the prefix of a path test. */\nconst underIssues = (\n\tcontext: Context,\n\tprefix: ReadonlyArray<Operand>,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst strings = Arr.map(prefix, (operand) =>\n\t\toperand._tag === \"Literal\" && typeof operand.value === \"string\"\n\t\t\t? operand.value\n\t\t\t: null,\n\t)\n\t// The test can match only a path that starts with the fixed strings of\n\t// the prefix, so the other resources are left out.\n\tconst candidates = Arr.filter(context.paths, (descriptor) =>\n\t\tmayStartWith(descriptor, strings),\n\t)\n\treturn Arr.flatMap(prefix, (operand, index): ReadonlyArray<Issue> => {\n\t\tconst place = [...at, \"prefix\", index]\n\t\tif (operand._tag === \"Literal\") {\n\t\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\t\treturn typeof kinds === \"object\" && !fitsLiteral(kinds, operand.value)\n\t\t\t})\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\t\tissue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []\n\t\t}\n\t\tconst principal = principalField(context, operand.path, place)\n\t\tif (isIssue(principal)) {\n\t\t\treturn [principal]\n\t\t}\n\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\treturn kinds === undefined\n\t\t\t\t? true\n\t\t\t\t: typeof kinds === \"string\"\n\t\t\t\t\t? !Catalog.accepts(principal.kinds, kinds)\n\t\t\t\t\t: !meet(kinds, principal.kinds)\n\t\t})\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\tissue: `The principal field \"${operand.path}\" can not hold a part of the path of the resource`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\t})\n}\n\nconst notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({\n\tpath: at,\n\tissue: \"The condition is not a whole condition tree\",\n})\n\n/**\n * The most issues that the walk of the conditions of one binding gives. A\n * policy may have very many wrong tests, and each issue keeps its path.\n */\nconst maxIssues = 100\n\n/** A path that the walk makes only when it needs it, for an issue. */\ntype LazyPath = () => ReadonlyArray<PropertyKey>\n\n/**\n * Puts the path of a node before the paths of its issues. A walk keeps no\n * path for a node with no issue, so a deep and wide condition costs little.\n */\nconst under = (\n\tmemo: Memo,\n\tat: LazyPath,\n\tissues: ReadonlyArray<Issue>,\n): ReadonlyArray<Issue> => {\n\tif (issues.length === 0) {\n\t\treturn issues\n\t}\n\tconst room = maxIssues - memo.issues.count\n\tmemo.issues.count += issues.length\n\tconst kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({\n\t\tpath: [...at(), ...issue.path],\n\t\tissue: issue.issue,\n\t}))\n\tconst first = memo.issues.count > maxIssues && !memo.issues.noted\n\tmemo.issues.noted ||= first\n\treturn first\n\t\t? [\n\t\t\t\t...kept,\n\t\t\t\t{\n\t\t\t\t\tpath: [],\n\t\t\t\t\tissue: `The policy has more problems. The first ${String(maxIssues)} are here.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: kept\n}\n\n/**\n * The issues of a condition. The constructors of the nodes checked the shape\n * of each node, so this checks the names and the types against the domain. A\n * node that the condition uses in many places is checked one time. The nodes\n * it has seen are in a native `Set`, because it must find a node by the node\n * itself: `Equal` compares nodes by structure.\n */\nconst conditionIssues = (\n\tcontext: Context,\n\tnode: Nodes.Node,\n\tat: LazyPath,\n\tseen: Set<Nodes.Node>,\n): ReadonlyArray<Issue> => {\n\tif (seen.has(node)) {\n\t\treturn []\n\t}\n\tseen.add(node)\n\tconst here = (issues: ReadonlyArray<Issue>) => under(context.memo, at, issues)\n\t// A test looks its field up in each resource of the rule. A rule for very\n\t// many kinds with very many tests is very much work, so it counts. Past\n\t// the limit, `ruleIssues` gives one issue for it, and the policy does not\n\t// load.\n\tconst room = (values: number) =>\n\t\tspendChecks(context.memo, 1 + values, context.named.length)\n\tswitch (node._tag) {\n\t\tcase \"Compare\": {\n\t\t\tif (!room(1)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\treturn here(\n\t\t\t\texists.length > 0\n\t\t\t\t\t? exists\n\t\t\t\t\t: operandIssues(\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tnode.field,\n\t\t\t\t\t\t\tdistinctFields(context, node.field),\n\t\t\t\t\t\t\tnode.op !== \"eq\" && node.op !== \"ne\",\n\t\t\t\t\t\t\tnode.value,\n\t\t\t\t\t\t\t[\"value\"],\n\t\t\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tcase \"In\": {\n\t\t\tif (!room(0)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\tif (exists.length > 0) {\n\t\t\t\treturn here(exists)\n\t\t\t}\n\t\t\tconst fields = distinctFields(context, node.field)\n\t\t\tif (!spendChecks(context.memo, node.values.length, fields.length)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn here(\n\t\t\t\tArr.flatMap(node.values, (operand, index) =>\n\t\t\t\t\toperandIssues(context, node.field, fields, false, operand, [\n\t\t\t\t\t\t\"values\",\n\t\t\t\t\t\tindex,\n\t\t\t\t\t]),\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tcase \"Exists\":\n\t\t\treturn room(0) ? here(readIssues(context, node.field, [\"field\"])) : []\n\t\tcase \"Under\":\n\t\t\treturn here(underIssues(context, node.prefix, []))\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn Arr.flatMap(node.conditions, (child, index) =>\n\t\t\t\tconditionIssues(\n\t\t\t\t\tcontext,\n\t\t\t\t\tchild,\n\t\t\t\t\t() => [...at(), \"conditions\", index],\n\t\t\t\t\tseen,\n\t\t\t\t),\n\t\t\t)\n\t\tcase \"Not\":\n\t\t\treturn conditionIssues(\n\t\t\t\tcontext,\n\t\t\t\tnode.condition,\n\t\t\t\t() => [...at(), \"condition\"],\n\t\t\t\tseen,\n\t\t\t)\n\t\tdefault:\n\t\t\treturn []\n\t}\n}\n\nconst hasAction = (\n\tdescriptor: Resource.Descriptor,\n\tactions: ReadonlyArray<string>,\n): boolean =>\n\tactions.includes(\"*\") ||\n\tArr.some(actions, (action) => descriptor.actions.includes(action))\n\n/**\n * Checks one rule against a domain. It gives one issue for each problem, up\n * to the most issues of the binding. `at` gives the path of the rule, and it\n * is made only for an issue that the binding keeps: a rule with very many\n * problems deep in a tree does not make a full path for each one.\n */\nexport const ruleIssues = (\n\tdomain: Domain.Any,\n\trule: RuleData,\n\tat: LazyPath,\n\tmemo: Memo = makeMemo(),\n): ReadonlyArray<Issue> => {\n\tconst here = (issues: ReadonlyArray<Issue>) => under(memo, at, issues)\n\tconst all = rule.resources.includes(\"*\")\n\t// Each kind is checked one time, also when the list names it again. The\n\t// names come from a policy, so a native `Set` finds the copies:\n\t// `Arr.dedupe` puts the names in groups by the hash of Effect, which has\n\t// no random seed, and very many names with one hash make one long group.\n\tconst names = Array.from(\n\t\tnew Set(Arr.filter(rule.resources, (kind) => kind !== \"*\")),\n\t)\n\tconst kinds = Array.from(HashMap.keys(domain.resources))\n\tconst named = Arr.flatMap(names, (kind) =>\n\t\tOption.toArray(HashMap.get(domain.resources, kind)),\n\t)\n\tconst known = all ? Array.from(HashMap.values(domain.resources)) : named\n\t// The rule never runs on a kind without one of its actions, so the types\n\t// of its tests are checked only on the kinds with one.\n\tconst applicable = Arr.filter(named, (descriptor) =>\n\t\thasAction(descriptor, rule.actions),\n\t)\n\tconst context: Context = {\n\t\tdomain,\n\t\tmemo,\n\t\tall,\n\t\tnamed,\n\t\tapplicable,\n\t\tpaths: all\n\t\t\t? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))\n\t\t\t: applicable,\n\t}\n\t// A native `Set` finds copies about ten times as fast as `Arr.dedupe`.\n\tconst actions = Array.from(\n\t\tnew Set(Arr.flatMap(known, (descriptor) => descriptor.actions)),\n\t)\n\tconst fieldIssues = Arr.flatMap(rule.fields ?? [], (field, index) =>\n\t\there(\n\t\t\tfield.includes(\"*\")\n\t\t\t\t? patternIssues(memo, known, field, [\"fields\", index])\n\t\t\t\t: fieldIssue(context, field, [\"fields\", index]),\n\t\t),\n\t)\n\tconst whenIssues =\n\t\trule.when === undefined\n\t\t\t? []\n\t\t\t: Nodes.isNode(rule.when)\n\t\t\t\t? conditionIssues(\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t\trule.when,\n\t\t\t\t\t\t() => [...at(), \"when\"],\n\t\t\t\t\t\tnew Set(),\n\t\t\t\t\t)\n\t\t\t\t: here([notWhole([\"when\"])])\n\t// The work is known only after every check of the rule. Past the limit, a\n\t// check gives no issue, so this issue must come, or the policy would load\n\t// with checks left out.\n\tconst work = memo.work.steps > Limits.searchSteps && !memo.work.noted\n\tmemo.work.noted ||= work\n\treturn [\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(names, (kind) => !HashMap.has(domain.resources, kind)),\n\t\t\t(kind: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"resources\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`There is no resource with the kind \"${kind}\"`,\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\"~kinds\",\n\t\t\t\t\t\t\t() => kinds,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(\n\t\t\t\trule.actions,\n\t\t\t\t(action) => action !== \"*\" && !actions.includes(action),\n\t\t\t),\n\t\t\t(action: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"actions\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`The action \"${action}\" is not an action of these resources`,\n\t\t\t\t\t\t\taction,\n\t\t\t\t\t\t\t`~actions\\u0000${names.join(\",\")}`,\n\t\t\t\t\t\t\t() => actions,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...fieldIssues,\n\t\t...whenIssues,\n\t\t// The issue of the work is not one of the most issues of the binding,\n\t\t// so a policy past the limit always says why.\n\t\t...(work\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at(),\n\t\t\t\t\t\tissue: `The policy takes more than ${String(Limits.searchSteps)} steps to check: its field patterns, and the fields and values of its tests on each resource. The most is ${String(Limits.searchSteps)}.`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/** The JSON form of a policy tree, or a policy tree, as the binding reads it. */\ntype TreeNode =\n\t| { readonly _tag: \"Rules\"; readonly rules: ReadonlyArray<RuleData> }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<TreeNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: TreeNode\n\t\t\treadonly policy: TreeNode\n\t }\n\n/** Checks each rule of a policy tree against a domain. */\nexport const treeIssues = (\n\tdomain: Domain.Any,\n\troot: TreeNode,\n): ReadonlyArray<Issue> => {\n\t// The rules come out of the tree first, and each one is checked after.\n\t// A check of a condition goes down its levels, so a check inside the walk\n\t// of the tree would go down the levels of both.\n\t// The place of each rule shares the path of its parent, and the full path\n\t// is made only when its rule is checked. A deep tree with very many rules\n\t// would need very much memory for a full copy of the path of each rule.\n\tconst rulesOf = (\n\t\tnode: TreeNode,\n\t\tplace: Place.Place | undefined,\n\t\tlevel: number,\n\t): ReadonlyArray<Issue | readonly [RuleData, Place.Place]> => {\n\t\tif (level > Limits.policyDepth) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: Place.pathOf(place),\n\t\t\t\t\tissue: `A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn Arr.map(\n\t\t\t\t\tnode.rules,\n\t\t\t\t\t(rule, index) => [rule, Place.at(place, \"rules\", index)] as const,\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn Arr.flatMap(node.policies, (child, index) =>\n\t\t\t\t\trulesOf(child, Place.at(place, \"policies\", index), level + 1),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn [\n\t\t\t\t\t...rulesOf(node.cap, Place.at(place, \"cap\"), level + 1),\n\t\t\t\t\t...rulesOf(node.policy, Place.at(place, \"policy\"), level + 1),\n\t\t\t\t]\n\t\t}\n\t}\n\tconst memo = makeMemo()\n\treturn Arr.flatMap(rulesOf(root, undefined, 1), (found) =>\n\t\tArray.isArray(found)\n\t\t\t? ruleIssues(domain, found[0], () => Place.pathOf(found[1]), memo)\n\t\t\t: [found as Issue],\n\t)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bind.js","names":["Catalog.accepts","Catalog.lookup","Patterns.parse","Patterns.fitsCatalog","Catalog.anyKinds","Place.pathOf","Trampoline.run","Place.at","at","Nodes.isNode","Trampoline.adaptive"],"sources":["../../src/internal/bind.ts"],"sourcesContent":["/**\n * The binding of rules to a domain. It is the one check of a rule, for a rule\n * from the builder, from `Policy.rule`, and from JSON alike. It reads the\n * catalogs of the resources and of the principal, and it gives one issue for\n * each problem, with the path of the problem and, for a name with a mistake,\n * the name that it may mean.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Patterns from \"./patterns.ts\"\nimport * as Place from \"./place.ts\"\nimport { withSuggestion } from \"./suggest.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\n/** One problem of a rule or a policy, with its path. */\nexport interface Issue {\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly issue: string\n}\n\n/** The data of a rule that the binding reads. */\nexport interface RuleData {\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: unknown\n\treadonly fields?: ReadonlyArray<string>\n}\n\n/**\n * The memo of one binding, for all its rules. A policy may name one wrong\n * field or one pattern in very many places, so the work on each is done one\n * time. It goes away when the binding ends.\n */\ninterface Memo {\n\t/**\n\t * The suggestion for each name, by the place where it was looked for. The\n\t * names come from a policy, so the map is native, as `fits` is.\n\t */\n\treadonly suggestions: Map<string, string>\n\t/**\n\t * If a pattern matches a field of a kind, by the kind and the pattern. The\n\t * patterns come from a policy, so the map is native: the hash of a string\n\t * in Effect has no random seed, and a policy could hold very many\n\t * patterns with one hash. The engine hashes a string with a random seed.\n\t */\n\treadonly fits: Map<string, boolean>\n\t/** The count of the issues of conditions so far. It belongs to one binding. */\n\treadonly issues: { count: number; noted: boolean }\n\t/** The steps of the checks of patterns so far. It belongs to one binding. */\n\treadonly work: { steps: number; noted: boolean }\n\t/** The limits of the domain. */\n\treadonly limits: Limits.Limits\n}\n\nconst makeMemo = (limits: Limits.Limits): Memo => ({\n\tsuggestions: new Map(),\n\tfits: new Map(),\n\tissues: { count: 0, noted: false },\n\twork: { steps: 0, noted: false },\n\tlimits,\n})\n\n/**\n * The most names that one binding makes a suggestion for. A suggestion\n * compares the name with every name of a catalog, so a policy with very many\n * wrong names would take a long time.\n */\nconst maxSuggestions = 32\n\n/** Adds a suggestion to a message, with the memo of the binding. */\nconst suggestIn = (\n\tmemo: Memo,\n\tmessage: string,\n\tname: string,\n\tscope: string,\n\tnames: () => ReadonlyArray<string>,\n): string => {\n\tconst key = `${scope}\\u0000${name}`\n\tconst known = memo.suggestions.get(key)\n\tif (known !== undefined) {\n\t\treturn known\n\t}\n\tif (memo.suggestions.size >= maxSuggestions) {\n\t\treturn message\n\t}\n\tconst made = withSuggestion(message, name, names())\n\tmemo.suggestions.set(key, made)\n\treturn made\n}\n\n/** The kinds that a rule may run on, from the view of its checks. */\ninterface Context {\n\treadonly domain: Domain.Any\n\treadonly memo: Memo\n\t/** The rule is for every resource (`\"*\"`). */\n\treadonly all: boolean\n\t/** The resources that the rule names and that the domain has. */\n\treadonly named: ReadonlyArray<Resource.Descriptor>\n\t/** The named resources that have one of the actions of the rule. */\n\treadonly applicable: ReadonlyArray<Resource.Descriptor>\n\t/**\n\t * The resources whose paths a path test may read: every resource with one\n\t * of the actions, for a rule for every resource.\n\t */\n\treadonly paths: ReadonlyArray<Resource.Descriptor>\n}\n\ntype Operand = Nodes.Operand\n\n/**\n * Tests if a field may hold a value of the type of a fixed value. A test with\n * `null` is a test that the field is missing, so `null` always fits. A number\n * fits any field of numbers: a test of an integer field with `1.5` or\n * `Infinity` has a meaning, as `lt` shows.\n */\nconst fitsLiteral = (kinds: Catalog.Kinds, value: unknown): boolean =>\n\tvalue === null ||\n\t(typeof value === \"number\" && kinds.number !== \"none\") ||\n\tCatalog.accepts(kinds, value)\n\n/**\n * Tests if two fields may hold one scalar. A principal field that is `null`\n * or that holds an object is a missing value, and a test with a missing value\n * is false, so only scalars count.\n */\nconst meet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(left.string && right.string) ||\n\t(left.boolean && right.boolean) ||\n\t(left.number !== \"none\" && right.number !== \"none\") ||\n\tArr.some(left.literals, (value) => Catalog.accepts(right, value)) ||\n\tArr.some(right.literals, (value) => Catalog.accepts(left, value))\n\nconst hasStrings = (kinds: Catalog.Kinds): boolean =>\n\tkinds.string || Arr.some(kinds.literals, (value) => typeof value === \"string\")\n\nconst hasNumbers = (kinds: Catalog.Kinds): boolean =>\n\tkinds.number !== \"none\" ||\n\tArr.some(kinds.literals, (value) => typeof value === \"number\")\n\n/** Tests if a field may hold a value with an order: a number or a string. */\nconst isOrdered = (kinds: Catalog.Kinds): boolean =>\n\thasStrings(kinds) || hasNumbers(kinds)\n\n/** Tests if two fields may hold values of one ordered type. */\nconst orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(hasStrings(left) && hasStrings(right)) ||\n\t(hasNumbers(left) && hasNumbers(right))\n\n/** The texts of the fields of a catalog that have only names, for suggestions. */\nconst namesOf = (catalog: Catalog.Catalog): ReadonlyArray<string> =>\n\tArr.map(\n\t\tArr.filter(catalog.fields, (field) =>\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t\t),\n\t\t(field) => field.text,\n\t)\n\n/** The issue of a field that is not a field of every resource of the rule. */\nconst fieldIssue = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst parts = context.memo.limits.fieldPathParts\n\tif (field.split(\".\", parts + 1).length > parts) {\n\t\treturn [\n\t\t\t{\n\t\t\t\tpath: at,\n\t\t\t\tissue: `The field path \"${field}\" has more than ${String(parts)} parts. The most is ${String(parts)}.`,\n\t\t\t},\n\t\t]\n\t}\n\tconst message = `The field \"${field}\" is not a field of every resource of the rule`\n\tif (context.all || context.named.length === 0) {\n\t\treturn [{ path: at, issue: message }]\n\t}\n\tconst without = Arr.filter(\n\t\tcontext.named,\n\t\t(descriptor) =>\n\t\t\tCatalog.lookup(descriptor.catalog, field, parts) === undefined,\n\t)\n\treturn without.length === 0\n\t\t? []\n\t\t: [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: suggestIn(context.memo, message, field, without[0]!.kind, () =>\n\t\t\t\t\t\tnamesOf(without[0]!.catalog),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t]\n}\n\n/**\n * The issues of a field that a condition reads: a field that is not a field\n * of every resource of the rule, or a field that is not plain data in one of\n * them. A read of a value that is not plain data, as a getter of a class, is\n * a defect in a decision. A residual may leave the read out, so the residual\n * would say \"yes\" where the decision fails. So the binding does not take\n * such a read: see `Catalog.Field.plain`.\n */\nconst readIssues = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst exists = fieldIssue(context, field, at)\n\tif (exists.length > 0) {\n\t\treturn exists\n\t}\n\treturn Arr.some(\n\t\tcontext.named,\n\t\t(descriptor) =>\n\t\t\t!(\n\t\t\t\tCatalog.lookup(\n\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\tfield,\n\t\t\t\t\tcontext.memo.limits.fieldPathParts,\n\t\t\t\t)?.plain ?? true\n\t\t\t),\n\t)\n\t\t? [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `A condition may not read the field \"${field}\". A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: []\n}\n\n/**\n * The issues of a field pattern: a pattern that does not parse, or that\n * matches no field of the resources of the rule, as a pattern with a mistake\n * in a name does.\n */\nconst patternIssues = (\n\tmemo: Memo,\n\tknown: ReadonlyArray<Resource.Descriptor>,\n\tpattern: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> =>\n\tResult.match(Patterns.parse(pattern, memo.limits.patternParts), {\n\t\tonFailure: (issue) => [{ path: at, issue }],\n\t\tonSuccess: (parsed) => {\n\t\t\t// A rule with no known resource has an issue for that already. The\n\t\t\t// pattern is checked on every kind, not only up to the first that it\n\t\t\t// fits: a decision on a kind does this check, so the limit of the\n\t\t\t// work below is a limit of the work of a decision too.\n\t\t\tconst fits =\n\t\t\t\tknown.length === 0 ||\n\t\t\t\tArr.reduce(known, false, (before, descriptor) => {\n\t\t\t\t\tconst key = `${descriptor.kind}\\u0000${pattern}`\n\t\t\t\t\treturn (\n\t\t\t\t\t\tOption.getOrElse(Option.fromUndefinedOr(memo.fits.get(key)), () => {\n\t\t\t\t\t\t\t// Each check of a pattern reads the steps of a catalog. The\n\t\t\t\t\t\t\t// work of one binding has a limit, so very many patterns stop\n\t\t\t\t\t\t\t// with one issue, and the policy does not load.\n\t\t\t\t\t\t\tmemo.work.steps +=\n\t\t\t\t\t\t\t\tMath.max(descriptor.catalog.edges, 1) *\n\t\t\t\t\t\t\t\tMath.max(parsed.length, 1)\n\t\t\t\t\t\t\tif (memo.work.steps > memo.limits.searchSteps) {\n\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst found = Patterns.fitsCatalog(\n\t\t\t\t\t\t\t\tparsed,\n\t\t\t\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\t\t\t\tmemo.limits.fieldPathParts,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tmemo.fits.set(key, found)\n\t\t\t\t\t\t\treturn found\n\t\t\t\t\t\t}) || before\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t// Past the limit of the work, `ruleIssues` gives one issue for it.\n\t\t\treturn fits || memo.work.steps > memo.limits.searchSteps\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field pattern \"${pattern}\" matches no field of the resources of the rule`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t},\n\t})\n\n/** The fields of the resources that a test of the rule may read. */\nconst fieldsAt = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArr.flatMap(context.applicable, (descriptor) => {\n\t\tconst found = Catalog.lookup(\n\t\t\tdescriptor.catalog,\n\t\t\tfield,\n\t\t\tcontext.memo.limits.fieldPathParts,\n\t\t)\n\t\treturn found === undefined ? [] : [found]\n\t})\n\n/** The field of the principal that an operand names, or the issue when there is none. */\nconst principalField = (\n\tcontext: Context,\n\tref: string,\n\tat: ReadonlyArray<PropertyKey>,\n): Catalog.Field | Issue => {\n\tconst found = Catalog.lookup(\n\t\tcontext.domain.principalCatalog,\n\t\tref,\n\t\tcontext.memo.limits.fieldPathParts,\n\t)\n\t// A read of the principal that is not plain data is a defect, as a read\n\t// of a resource is: see `readIssues`.\n\tif (found !== undefined && !found.plain) {\n\t\treturn {\n\t\t\tpath: at,\n\t\t\tissue: `A condition may not read the field \"${ref}\" of the principal. A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t}\n\t}\n\treturn found !== undefined\n\t\t? found\n\t\t: {\n\t\t\t\tpath: at,\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tcontext.memo,\n\t\t\t\t\t`The principal has no field \"${ref}\"`,\n\t\t\t\t\tref,\n\t\t\t\t\t\"~principal\",\n\t\t\t\t\t() => namesOf(context.domain.principalCatalog),\n\t\t\t\t),\n\t\t\t}\n}\n\nconst isIssue = (value: Catalog.Field | Issue): value is Issue =>\n\t\"issue\" in value\n\n/**\n * The fields that a test reads, with one field for each distinct kind of\n * value. A rule for very many kinds may have one field type in all of them,\n * so each value of the test is checked one time for each distinct kind, not\n * one time for each resource. The kinds are found by their text in a native\n * `Map`, which belongs to this call.\n */\nconst distinctFields = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArray.from(\n\t\tnew Map(\n\t\t\tArr.map(\n\t\t\t\tfieldsAt(context, field),\n\t\t\t\t(found) => [JSON.stringify(found.kinds), found] as const,\n\t\t\t),\n\t\t).values(),\n\t)\n\n/**\n * Counts the work of the checks of the values of a test, as the work of the\n * field patterns counts. Past the limit, the values are not checked, and\n * `ruleIssues` gives one issue for the work.\n */\nconst spendChecks = (memo: Memo, values: number, fields: number): boolean => {\n\tmemo.work.steps += values * Math.max(fields, 1)\n\treturn memo.work.steps <= memo.limits.searchSteps\n}\n\n/** The issues of one operand of a test of a field, with its distinct fields. */\nconst operandIssues = (\n\tcontext: Context,\n\tfield: string,\n\tfields: ReadonlyArray<Catalog.Field>,\n\tordered: boolean,\n\toperand: Operand,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tif (operand._tag === \"Literal\") {\n\t\tconst value = operand.value\n\t\tif (ordered && typeof value !== \"string\" && typeof value !== \"number\") {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t...(Arr.some(fields, (found) => !fitsLiteral(found.kinds, value))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" can not hold the value ${JSON.stringify(value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t\t...(ordered && Arr.some(fields, (found) => !isOrdered(found.kinds))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t]\n\t}\n\tconst principal = principalField(context, operand.path, at)\n\tif (isIssue(principal)) {\n\t\treturn [principal]\n\t}\n\treturn [\n\t\t...(Arr.some(fields, (found) => !meet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" can not hold a value of the principal field \"${operand.path}\"`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(ordered &&\n\t\tArr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/**\n * Tests if the path of a resource may start with a prefix: it is as long as\n * the prefix, and each fixed string of the prefix is the string of the path\n * at its place, when the path has a string there.\n */\nconst mayStartWith = (\n\tdescriptor: Resource.Descriptor,\n\tstrings: ReadonlyArray<string | null>,\n): boolean =>\n\t(descriptor.path?.length ?? 0) >= strings.length &&\n\tArr.every(strings, (string, index) => {\n\t\tconst segment = descriptor.path?.[index]\n\t\treturn string === null || typeof segment !== \"string\" || segment === string\n\t})\n\n/** The kinds of the part of the path of a resource at a place. */\nconst pathPartKinds = (\n\tdescriptor: Resource.Descriptor,\n\tindex: number,\n): Catalog.Kinds | string | undefined => {\n\tconst segment = descriptor.path?.[index]\n\treturn segment === undefined || typeof segment === \"string\"\n\t\t? segment\n\t\t: (Catalog.lookup(descriptor.catalog, segment.field)?.kinds ??\n\t\t\t\tCatalog.anyKinds)\n}\n\n/** The issues of the prefix of a path test. */\nconst underIssues = (\n\tcontext: Context,\n\tprefix: ReadonlyArray<Operand>,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst strings = Arr.map(prefix, (operand) =>\n\t\toperand._tag === \"Literal\" && typeof operand.value === \"string\"\n\t\t\t? operand.value\n\t\t\t: null,\n\t)\n\t// The test can match only a path that starts with the fixed strings of\n\t// the prefix, so the other resources are left out.\n\tconst candidates = Arr.filter(context.paths, (descriptor) =>\n\t\tmayStartWith(descriptor, strings),\n\t)\n\treturn Arr.flatMap(prefix, (operand, index): ReadonlyArray<Issue> => {\n\t\tconst place = [...at, \"prefix\", index]\n\t\tif (operand._tag === \"Literal\") {\n\t\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\t\treturn typeof kinds === \"object\" && !fitsLiteral(kinds, operand.value)\n\t\t\t})\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\t\tissue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []\n\t\t}\n\t\tconst principal = principalField(context, operand.path, place)\n\t\tif (isIssue(principal)) {\n\t\t\treturn [principal]\n\t\t}\n\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\treturn kinds === undefined\n\t\t\t\t? true\n\t\t\t\t: typeof kinds === \"string\"\n\t\t\t\t\t? !Catalog.accepts(principal.kinds, kinds)\n\t\t\t\t\t: !meet(kinds, principal.kinds)\n\t\t})\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\tissue: `The principal field \"${operand.path}\" can not hold a part of the path of the resource`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\t})\n}\n\nconst notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({\n\tpath: at,\n\tissue: \"The condition is not a whole condition tree\",\n})\n\n/**\n * The most issues that the walk of the conditions of one binding gives. A\n * policy may have very many wrong tests, and each issue keeps its path.\n */\nconst maxIssues = 100\n\n/**\n * The place of a part of a policy, which a walk turns into a full path only\n * for an issue: see `Place`.\n */\ntype At = Place.Place | undefined\n\n/**\n * Puts the path of a node before the paths of its issues. A walk keeps no\n * path for a node with no issue, so a deep and wide condition costs little.\n */\nconst under = (\n\tmemo: Memo,\n\tat: At,\n\tissues: ReadonlyArray<Issue>,\n): ReadonlyArray<Issue> => {\n\tif (issues.length === 0) {\n\t\treturn issues\n\t}\n\tconst room = maxIssues - memo.issues.count\n\tmemo.issues.count += issues.length\n\tconst kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({\n\t\tpath: [...Place.pathOf(at), ...issue.path],\n\t\tissue: issue.issue,\n\t}))\n\tconst first = memo.issues.count > maxIssues && !memo.issues.noted\n\tmemo.issues.noted ||= first\n\treturn first\n\t\t? [\n\t\t\t\t...kept,\n\t\t\t\t{\n\t\t\t\t\tpath: [],\n\t\t\t\t\tissue: `The policy has more problems. The first ${String(maxIssues)} are here.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: kept\n}\n\n/**\n * The issues of a condition. The constructors of the nodes checked the shape\n * of each node, so this checks the names and the types against the domain. A\n * node that the condition uses in many places is checked one time. The nodes\n * it has seen are in a native `Set`, because it must find a node by the node\n * itself: `Equal` compares nodes by structure.\n */\nconst conditionIssues = (\n\tcontext: Context,\n\troot: Nodes.Node,\n\trootAt: At,\n\tseen: Set<Nodes.Node>,\n): ReadonlyArray<Issue> =>\n\t// The walk takes the steps of a trampoline, so a deep condition does not\n\t// fill the stack.\n\tTrampoline.run(root.depth, (R) => {\n\t\t// A test looks its field up in each resource of the rule. A rule for\n\t\t// very many kinds with very many tests is very much work, so it counts.\n\t\t// Past the limit, `ruleIssues` gives one issue for it, and the policy\n\t\t// does not load.\n\t\tconst room = (values: number) =>\n\t\t\tspendChecks(context.memo, 1 + values, context.named.length)\n\t\tconst leaf = (node: Nodes.Node, at: At): ReadonlyArray<Issue> => {\n\t\t\tconst here = (issues: ReadonlyArray<Issue>) =>\n\t\t\t\tunder(context.memo, at, issues)\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"Compare\": {\n\t\t\t\t\tif (!room(1)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\t\t\treturn here(\n\t\t\t\t\t\texists.length > 0\n\t\t\t\t\t\t\t? exists\n\t\t\t\t\t\t\t: operandIssues(\n\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t\tnode.field,\n\t\t\t\t\t\t\t\t\tdistinctFields(context, node.field),\n\t\t\t\t\t\t\t\t\tnode.op !== \"eq\" && node.op !== \"ne\",\n\t\t\t\t\t\t\t\t\tnode.value,\n\t\t\t\t\t\t\t\t\t[\"value\"],\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"In\": {\n\t\t\t\t\tif (!room(0)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\t\t\tif (exists.length > 0) {\n\t\t\t\t\t\treturn here(exists)\n\t\t\t\t\t}\n\t\t\t\t\tconst fields = distinctFields(context, node.field)\n\t\t\t\t\tif (!spendChecks(context.memo, node.values.length, fields.length)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\treturn here(\n\t\t\t\t\t\tArr.flatMap(node.values, (operand, index) =>\n\t\t\t\t\t\t\toperandIssues(context, node.field, fields, false, operand, [\n\t\t\t\t\t\t\t\t\"values\",\n\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Exists\":\n\t\t\t\t\treturn room(0) ? here(readIssues(context, node.field, [\"field\"])) : []\n\t\t\t\tcase \"Under\":\n\t\t\t\t\treturn here(underIssues(context, node.prefix, []))\n\t\t\t\tdefault:\n\t\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tconst go = (\n\t\t\tnode: Nodes.Node,\n\t\t\tat: At,\n\t\t): Trampoline.Step<ReadonlyArray<Issue>> => {\n\t\t\tif (seen.has(node)) {\n\t\t\t\treturn R.succeed([])\n\t\t\t}\n\t\t\tseen.add(node)\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"And\":\n\t\t\t\tcase \"Or\":\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(node.conditions, (child, index) =>\n\t\t\t\t\t\t\tR.suspend(() => go(child, Place.at(at, \"conditions\", index))),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tArr.flatten,\n\t\t\t\t\t)\n\t\t\t\tcase \"Not\":\n\t\t\t\t\treturn R.suspend(() => go(node.condition, Place.at(at, \"condition\")))\n\t\t\t\tdefault:\n\t\t\t\t\treturn R.succeed(leaf(node, at))\n\t\t\t}\n\t\t}\n\t\treturn go(root, rootAt)\n\t})\n\nconst hasAction = (\n\tdescriptor: Resource.Descriptor,\n\tactions: ReadonlyArray<string>,\n): boolean =>\n\tactions.includes(\"*\") ||\n\tArr.some(actions, (action) => descriptor.actions.includes(action))\n\n/**\n * Checks one rule against a domain. It gives one issue for each problem, up\n * to the most issues of the binding. `at` is the place of the rule, and its\n * full path is made only for an issue that the binding keeps: a rule with\n * very many problems deep in a tree does not make a full path for each one.\n */\nexport const ruleIssues = (\n\tdomain: Domain.Any,\n\trule: RuleData,\n\tat: At,\n\tmemo: Memo = makeMemo(domain.limits),\n): ReadonlyArray<Issue> => {\n\tconst here = (issues: ReadonlyArray<Issue>) => under(memo, at, issues)\n\tconst all = rule.resources.includes(\"*\")\n\t// Each kind is checked one time, also when the list names it again. The\n\t// names come from a policy, so a native `Set` finds the copies:\n\t// `Arr.dedupe` puts the names in groups by the hash of Effect, which has\n\t// no random seed, and very many names with one hash make one long group.\n\tconst names = Array.from(\n\t\tnew Set(Arr.filter(rule.resources, (kind) => kind !== \"*\")),\n\t)\n\tconst kinds = Array.from(HashMap.keys(domain.resources))\n\tconst named = Arr.flatMap(names, (kind) =>\n\t\tOption.toArray(HashMap.get(domain.resources, kind)),\n\t)\n\tconst known = all ? Array.from(HashMap.values(domain.resources)) : named\n\t// The rule never runs on a kind without one of its actions, so the types\n\t// of its tests are checked only on the kinds with one.\n\tconst applicable = Arr.filter(named, (descriptor) =>\n\t\thasAction(descriptor, rule.actions),\n\t)\n\tconst context: Context = {\n\t\tdomain,\n\t\tmemo,\n\t\tall,\n\t\tnamed,\n\t\tapplicable,\n\t\tpaths: all\n\t\t\t? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))\n\t\t\t: applicable,\n\t}\n\t// A native `Set` finds copies about ten times as fast as `Arr.dedupe`.\n\tconst actions = Array.from(\n\t\tnew Set(Arr.flatMap(known, (descriptor) => descriptor.actions)),\n\t)\n\tconst fieldIssues = Arr.flatMap(rule.fields ?? [], (field, index) =>\n\t\there(\n\t\t\tfield.includes(\"*\")\n\t\t\t\t? patternIssues(memo, known, field, [\"fields\", index])\n\t\t\t\t: fieldIssue(context, field, [\"fields\", index]),\n\t\t),\n\t)\n\tconst conditionNodes = memo.limits.conditionNodes\n\tconst whenIssues =\n\t\trule.when === undefined\n\t\t\t? []\n\t\t\t: !Nodes.isNode(rule.when)\n\t\t\t\t? here([notWhole([\"when\"])])\n\t\t\t\t: // The constructors of the nodes check the ceiling, so the binding\n\t\t\t\t\t// checks the limit of the domain.\n\t\t\t\t\trule.when.weight > conditionNodes\n\t\t\t\t\t? here([\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: [\"when\"],\n\t\t\t\t\t\t\t\tissue: `A condition has ${String(rule.when.weight)} nodes and values. The most is ${String(conditionNodes)}.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t])\n\t\t\t\t\t: conditionIssues(context, rule.when, Place.at(at, \"when\"), new Set())\n\t// The work is known only after every check of the rule. Past the limit, a\n\t// check gives no issue, so this issue must come, or the policy would load\n\t// with checks left out.\n\tconst work = memo.work.steps > memo.limits.searchSteps && !memo.work.noted\n\tmemo.work.noted ||= work\n\treturn [\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(names, (kind) => !HashMap.has(domain.resources, kind)),\n\t\t\t(kind: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"resources\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`There is no resource with the kind \"${kind}\"`,\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\"~kinds\",\n\t\t\t\t\t\t\t() => kinds,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(\n\t\t\t\trule.actions,\n\t\t\t\t(action) => action !== \"*\" && !actions.includes(action),\n\t\t\t),\n\t\t\t(action: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"actions\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`The action \"${action}\" is not an action of these resources`,\n\t\t\t\t\t\t\taction,\n\t\t\t\t\t\t\t`~actions\\u0000${names.join(\",\")}`,\n\t\t\t\t\t\t\t() => actions,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...fieldIssues,\n\t\t...whenIssues,\n\t\t// The issue of the work is not one of the most issues of the binding,\n\t\t// so a policy past the limit always says why.\n\t\t...(work\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\tissue: `The policy takes more than ${String(memo.limits.searchSteps)} steps to check: its field patterns, and the fields and values of its tests on each resource. The most is ${String(memo.limits.searchSteps)}.`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/** The JSON form of a policy tree, or a policy tree, as the binding reads it. */\ntype TreeNode =\n\t| { readonly _tag: \"Rules\"; readonly rules: ReadonlyArray<RuleData> }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<TreeNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: TreeNode\n\t\t\treadonly policy: TreeNode\n\t }\n\n/** Checks each rule of a policy tree against a domain. */\nexport const treeIssues = (\n\tdomain: Domain.Any,\n\troot: TreeNode,\n): ReadonlyArray<Issue> => {\n\t// The rules come out of the tree first, and each one is checked after.\n\t// A check of a condition goes down its levels, so a check inside the walk\n\t// of the tree would go down the levels of both. The place of each rule\n\t// shares the path of its parent. The tree may be the JSON form, with no\n\t// depth that a node knows, so the walk is a walk of the trampoline for an\n\t// input of an unknown depth.\n\ttype Found = Issue | readonly [RuleData, Place.Place]\n\tconst rulesOf = Trampoline.adaptive<\n\t\t{ readonly node: TreeNode; readonly at: At },\n\t\tReadonlyArray<Found>\n\t>((R, child) => ({ node, at }, level) => {\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tArr.map(\n\t\t\t\t\t\tnode.rules,\n\t\t\t\t\t\t(rule, index) => [rule, Place.at(at, \"rules\", index)] as const,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(node.policies, (inner, index) =>\n\t\t\t\t\t\tchild(\n\t\t\t\t\t\t\t{ node: inner, at: Place.at(at, \"policies\", index) },\n\t\t\t\t\t\t\tlevel + 1,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tArr.flatten,\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t{ node: node.cap, at: Place.at(at, \"cap\") },\n\t\t\t\t\t\t\t{ node: node.policy, at: Place.at(at, \"policy\") },\n\t\t\t\t\t\t],\n\t\t\t\t\t\t(part) => child(part, level + 1),\n\t\t\t\t\t),\n\t\t\t\t\tArr.flatten,\n\t\t\t\t)\n\t\t}\n\t})\n\tconst memo = makeMemo(domain.limits)\n\treturn Arr.flatMap(rulesOf({ node: root, at: undefined }), (found) =>\n\t\tArray.isArray(found)\n\t\t\t? ruleIssues(domain, found[0], found[1], memo)\n\t\t\t: [found as Issue],\n\t)\n}\n"],"mappings":"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"}
@@ -40,32 +40,55 @@ export interface Field {
40
40
  * above the field may be an object that does not have it as a data field:
41
41
  * a class without the field, which may have a getter of that name, a
42
42
  * value such as a date or a list, or any object, as of `Schema.Unknown`.
43
- * A decode or a `make` copies a struct or a record into a plain object,
44
- * so a struct without the field gives `null` for it.
43
+ * A struct of a union without the field makes it not plain too, since a
44
+ * value made by hand may have it with any value.
45
45
  */
46
46
  readonly plain: boolean;
47
47
  }
48
- /** A place in the tree of a catalog, with the places below it. */
48
+ /**
49
+ * A place in the graph of a catalog. A path of a field leads to a place, and
50
+ * many paths may lead to one place, as in a recursive Schema: `next` and
51
+ * `next.next` of a list of links are one place. A place keeps the ids of the
52
+ * places below it, so the graph has no loop of objects.
53
+ */
49
54
  export interface Branch {
50
- /** The field at this place. The root has none. */
51
- readonly field: Field | undefined;
52
- /** The places below, by name, in the order of the Schema. */
53
- readonly children: ReadonlyArray<readonly [string, Branch]>;
55
+ readonly id: number;
56
+ /** The kinds of the values of the field at this place. */
57
+ readonly kinds: Kinds;
58
+ /** The field holds a scalar, a record or a list: see `Field.listed`. */
59
+ readonly values: boolean;
60
+ /** The field may hold a struct. */
61
+ readonly structs: boolean;
62
+ readonly open: boolean;
63
+ readonly plain: boolean;
64
+ /** The ids of the places below, by name, in the order of the Schema. */
65
+ readonly children: ReadonlyArray<readonly [string, number]>;
54
66
  /**
55
67
  * The places below, by name. A lookup gets each name from `split`, as a
56
68
  * new string, and a `HashMap` finds a new string about 20% faster than a
57
69
  * native `Map`. The names come from the Schema, so a name from a policy
58
70
  * can not make a long list of names with one hash here.
59
71
  */
60
- readonly names: HashMap.HashMap<string, Branch>;
72
+ readonly names: HashMap.HashMap<string, number>;
61
73
  /** The place for the keys of a record, when the value may be a record. */
62
74
  readonly key: Key | undefined;
63
- readonly keyBranch: Branch | undefined;
75
+ readonly keyBranch: number | undefined;
64
76
  }
65
77
  /** The catalog of the fields of a Schema. */
66
78
  export interface Catalog {
67
- /** Every field, a field before the fields below it. */
79
+ /**
80
+ * The fields that a list shows, a field before the fields below it. For a
81
+ * recursive Schema, the list goes into each recursive place one time on
82
+ * each path, so it is finite.
83
+ */
68
84
  readonly fields: ReadonlyArray<Field>;
85
+ /** The places, by id. The root is the place with the id 0. */
86
+ readonly branches: ReadonlyArray<Branch>;
87
+ /**
88
+ * The number of steps from a place to a place below it. A search of the
89
+ * catalog, as for a field pattern, reads each one, so this is its work.
90
+ */
91
+ readonly edges: number;
69
92
  readonly root: Branch;
70
93
  }
71
94
  //# sourceMappingURL=catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","names":[],"sources":["../../src/internal/catalog.ts"],"mappings":";;;iBAsBiB;;WAEP;WACA;;WAEA;;WAEA;;WAEA;;WAEA,UAAU;;;iBA8EH;WACP;;WAEA,YAAY,cAAc,UAAU;;;YAIlC,mBAAmB;;iBAGd;WACP,MAAM,cAAc;;WAEpB;WACA,OAAO;;;;;WAKP;;WAEA;;;;;;;;;WASA;;;iBAIO;;WAEP,OAAO;;WAEP,UAAU,gCAAgC;;;;;;;WAO1C,OAAO,QAAQ,gBAAgB;;WAE/B,KAAK;WACL,WAAW;;;iBAIJ;;WAEP,QAAQ,cAAc;WACtB,MAAM"}
1
+ {"version":3,"file":"catalog.d.ts","names":[],"sources":["../../src/internal/catalog.ts"],"mappings":";;;iBAuBiB;;WAEP;WACA;;WAEA;;WAEA;;WAEA;;WAEA,UAAU;;;iBA8EH;WACP;;WAEA,YAAY,cAAc,UAAU;;;YAIlC,mBAAmB;;iBAGd;WACP,MAAM,cAAc;;WAEpB;WACA,OAAO;;;;;WAKP;;WAEA;;;;;;;;;WASA;;;;;;;;iBASO;WACP;;WAEA,OAAO;;WAEP;;WAEA;WACA;WACA;;WAEA,UAAU;;;;;;;WAOV,OAAO,QAAQ;;WAEf,KAAK;WACL;;;iBAIO;;;;;;WAMP,QAAQ,cAAc;;WAEtB,UAAU,cAAc;;;;;WAKxB;WACA,MAAM"}