@astrale-os/kernel-dsl 1.0.0-beta.14 → 1.0.0-beta.15

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 (57) hide show
  1. package/dist/v1/addressing/domain-dependency.d.ts +0 -6
  2. package/dist/v1/addressing/domain-dependency.js +1 -6
  3. package/dist/v1/builder/class/authoring.d.ts +43 -0
  4. package/dist/v1/builder/index.d.ts +1 -1
  5. package/dist/v1/builder/lowering/policy.js +1 -0
  6. package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
  7. package/dist/v1/builder/lowering/schema/context.js +22 -0
  8. package/dist/v1/builder/policy/authoring.d.ts +84 -4
  9. package/dist/v1/builder/policy/authoring.js +41 -12
  10. package/dist/v1/builder/schema/projection/class.d.ts +116 -14
  11. package/dist/v1/bundle/codec.js +7 -7
  12. package/dist/v1/compiled/admission/accept.js +3 -3
  13. package/dist/v1/compiled/admission/closure.js +2 -2
  14. package/dist/v1/compiled/admission/semantics/core.js +3 -7
  15. package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
  16. package/dist/v1/language/core/facts.d.ts +17 -4
  17. package/dist/v1/language/core/facts.js +26 -5
  18. package/dist/v1/language/limits.d.ts +64 -0
  19. package/dist/v1/language/limits.js +60 -0
  20. package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
  21. package/dist/v1/language/policy/analysis/budgets.js +30 -20
  22. package/dist/v1/language/policy/analysis/checks.js +3 -2
  23. package/dist/v1/language/policy/analysis/typing.js +4 -3
  24. package/dist/v1/language/validation/rule.d.ts +3 -0
  25. package/dist/v1/language/validation/rule.js +5 -1
  26. package/dist/v1/language/value/regexp/ast.js +6 -7
  27. package/dist/v1/language/value/regexp/compile.d.ts +5 -11
  28. package/dist/v1/language/value/regexp/compile.js +14 -12
  29. package/dist/v1/language/value/regexp/syntax.js +3 -2
  30. package/dist/v1/schema/admission/accept.js +3 -14
  31. package/dist/v1/schema/admission/callable.d.ts +21 -0
  32. package/dist/v1/schema/admission/callable.js +96 -0
  33. package/dist/v1/schema/index.d.ts +7 -0
  34. package/dist/v1/schema/index.js +3 -0
  35. package/dist/v1/schema/normalization/domain.d.ts +2 -0
  36. package/dist/v1/schema/normalization/domain.js +1 -1
  37. package/dist/v1/schema/resolution/context.js +7 -7
  38. package/dist/v1/schema/resolution/domain.d.ts +5 -0
  39. package/dist/v1/schema/resolution/domain.js +1 -1
  40. package/dist/v1/schema/rule.d.ts +3 -0
  41. package/dist/v1/schema/rule.js +16 -3
  42. package/dist/v1/schema/validation/callable.d.ts +33 -3
  43. package/dist/v1/schema/validation/callable.js +49 -15
  44. package/dist/v1/schema/validation/core.js +16 -10
  45. package/dist/v1/schema/validation/domain.js +2 -2
  46. package/dist/v1/schema/validation/domain.route.d.ts +6 -1
  47. package/dist/v1/schema/validation/domain.route.js +18 -0
  48. package/dist/v1/schema/validation/domain.structure.d.ts +28 -26
  49. package/dist/v1/schema/validation/domain.structure.js +22 -6
  50. package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
  51. package/dist/v1/schema/validation/policy/budgets.js +89 -16
  52. package/dist/v1/schema/validation/policy/references.js +2 -1
  53. package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
  54. package/dist/v1/schema/validation/policy/uses.js +28 -48
  55. package/dist/v1/schema/validation/value.d.ts +3 -0
  56. package/dist/v1/schema/validation/value.js +3 -9
  57. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { Key } from '../../../addressing/index.js';
2
2
  import { isMethodExecutable } from '../../../language/callable/executable.js';
3
3
  import { coveringEdgeUniqueTuples, coveringNodeUniqueTuples, edgeUniqueKeys, nodeUniqueKeys, } from '../../../language/class/unique.js';
4
- import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, } from '../../../language/core/facts.js';
4
+ import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, DOMAIN_ENDPOINT_CLASS, } from '../../../language/core/facts.js';
5
5
  import {} from '../../../language/core/model/core.js';
6
6
  import { policyObjectOrigin, policyObjectKey, projectedMemberClass, } from '../../../language/index.js';
7
7
  import { bindValueProgram } from '../../../language/value/compile/load.js';
@@ -123,7 +123,7 @@ function validateCoreProperties(values, analysis, path) {
123
123
  }
124
124
  function resolveCoreEndpoint(context, entry, ref, nodes, path) {
125
125
  if (ref.kind === 'domain')
126
- return { key: 'domain' };
126
+ return { key: 'domain', member: { class: DOMAIN_ENDPOINT_CLASS, pointer: path } };
127
127
  const undeclared = () => fail(path, 'Core endpoint does not name a declared local Definition.');
128
128
  if (policyObjectOrigin(ref) !== entry.origin)
129
129
  return undeclared();
@@ -151,9 +151,5 @@ function declaresProjectedMember(context, entry, ref) {
151
151
  return declarations.some((declaration) => declaration.ref.name === ref.name);
152
152
  }
153
153
  function endpointSatisfies(context, endpoint, accepts) {
154
- if ('value' in endpoint)
155
- return classSatisfiesAny(context, endpoint.value.class, accepts);
156
- if ('member' in endpoint)
157
- return classSatisfiesAny(context, endpoint.member.class, accepts);
158
- return true;
154
+ return classSatisfiesAny(context, 'value' in endpoint ? endpoint.value.class : endpoint.member.class, accepts);
159
155
  }
@@ -1,6 +1,6 @@
1
1
  import {} from '../../../../addressing/index.js';
2
2
  import { branchesOf, targetMode, isConnected, } from '../../../../language/policy/analysis/branches.js';
3
- import { MAX_PATTERN_DEPTH, MAX_BRANCHES, MAX_EDGES, MAX_DOMAINS, measureChecks, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, policyMeasurer, } from '../../../../language/policy/analysis/budgets.js';
3
+ import { exceededLimits, measureChecks, policyMeasurer, } from '../../../../language/policy/analysis/budgets.js';
4
4
  import { neverAdmittedClasses, } from '../../../../language/policy/analysis/checks.js';
5
5
  import {} from '../../../../language/policy/analysis/typing.js';
6
6
  import { checksNameTheCaller, isClassRuleChecks, } from '../../../../language/policy/model/policy.js';
@@ -28,11 +28,7 @@ export function validatePolicyParity(context, entry, entryIndex, policies, origi
28
28
  }
29
29
  }
30
30
  const measure = measurePolicy(name);
31
- if (measure !== undefined &&
32
- (measure.depth > MAX_PATTERN_DEPTH ||
33
- measure.edgeCounts.length > MAX_BRANCHES ||
34
- Math.max(0, ...measure.edgeCounts) > MAX_EDGES ||
35
- measure.domains.size > MAX_DOMAINS)) {
31
+ if (measure !== undefined && exceededLimits(measure).length > 0) {
36
32
  fail(path, 'Policy expansion exceeds the V1 depth, branch, Edge, or Domain budget.');
37
33
  }
38
34
  const branches = typing.expandedBranches(name, `$${name}`);
@@ -98,10 +94,7 @@ function validatePolicyChecks(context, typing, entry, expression, input, receive
98
94
  if (!checksNameTheCaller(expression)) {
99
95
  fail(path, `${SITE[site].checks} must check a Policy on every branch: sameNode alone names no caller.`);
100
96
  }
101
- const measure = measureChecks(expression);
102
- if (measure.depth > MAX_CHECK_DEPTH ||
103
- measure.branches > MAX_BRANCHES ||
104
- measure.leaves > MAX_CHECK_LEAVES) {
97
+ if (exceededLimits(measureChecks(expression)).length > 0) {
105
98
  fail(path, `${SITE[site].checks} exceed the V1 depth, branch, or leaf budget.`);
106
99
  }
107
100
  const leaves = new Map();
@@ -1,9 +1,17 @@
1
1
  import type { ClassRef } from '../../addressing/index.js';
2
2
  import type { EdgeContract, EdgeCount, EdgeEndpointBase, IncidentEndpoint } from '../class/model/edge.js';
3
+ /**
4
+ * The Class of the owning Domain's Node: every Domain principal is a Node of the final Kernel
5
+ * `Domain` Class, so a Core Edge that names the Domain must accept that Class at that endpoint.
6
+ */
7
+ export declare const DOMAIN_ENDPOINT_CLASS: ClassRef;
3
8
  /** One endpoint of a Core Edge: a Core Node, a projected member or the owning Domain. */
4
9
  export interface CoreFactEndpoint {
5
10
  readonly key: string;
6
- /** Set on a projected member: its Kernel Class, and where a breach of its degree is reported. */
11
+ /**
12
+ * Set on a projected member and on the owning Domain: the Kernel Class it is a Node of, and where
13
+ * a breach of its degree is reported.
14
+ */
7
15
  readonly member?: {
8
16
  readonly class: ClassRef;
9
17
  readonly pointer: string;
@@ -26,10 +34,12 @@ export interface CoreDegreeSubject {
26
34
  }
27
35
  export interface CoreEdgeSet<Edge> {
28
36
  readonly edges: readonly CoreFactEdge<Edge>[];
29
- /** The local exact Edge Classes, in the order their slices are checked. */
37
+ /** The local Edge Classes, in the order their slices are checked. */
30
38
  readonly classes: readonly {
31
39
  readonly key: string;
32
- readonly contract: EdgeContract;
40
+ readonly contract: EdgeContract & {
41
+ readonly abstract: boolean;
42
+ };
33
43
  }[];
34
44
  readonly nodes: readonly CoreDegreeSubject[];
35
45
  readonly satisfies: (ref: ClassRef, accepts: readonly ClassRef[]) => boolean;
@@ -68,5 +78,8 @@ export declare function coreEndpointsSatisfy<Endpoint>(contract: EdgeContract, s
68
78
  * DV-EDGE-UNIQUE); Activation checks them against stored data.
69
79
  */
70
80
  export declare function checkHeldKeys<Fact>(facts: Iterable<Fact>, keys: (fact: Fact) => readonly string[], report: (fact: Fact, previous: Fact) => void): void;
71
- /** Check normalized Edge identity, noSelf, keys, exact-Class acyclicity and Core degrees. */
81
+ /**
82
+ * Check normalized Edge identity, noSelf, keys, and the acyclicity and Core degrees of each exact
83
+ * concrete Edge Class over its own Edges.
84
+ */
72
85
  export declare function checkCoreEdgeSet<Edge>(set: CoreEdgeSet<Edge>): void;
@@ -1,5 +1,18 @@
1
1
  import { compareUnicode } from '../../addressing/ordering.js';
2
- import { countBounds, hasDirectedCycle } from '../class/inheritance.js';
2
+ import { countBounds, hasDirectedCycle, KERNEL_ORIGIN } from '../class/inheritance.js';
3
+ /*
4
+ * The Core fact rules that authored and compiled admission share. Each admission resolves its own
5
+ * Core Nodes and Edges, then reports every issue under its own codes, messages and pointers.
6
+ */
7
+ /**
8
+ * The Class of the owning Domain's Node: every Domain principal is a Node of the final Kernel
9
+ * `Domain` Class, so a Core Edge that names the Domain must accept that Class at that endpoint.
10
+ */
11
+ export const DOMAIN_ENDPOINT_CLASS = Object.freeze({
12
+ origin: KERNEL_ORIGIN,
13
+ kind: 'class',
14
+ name: 'Domain',
15
+ });
3
16
  /** Whether the endpoints satisfy the contract: in order when directed, in either order when not. */
4
17
  export function coreEndpointsSatisfy(contract, source, target, satisfies) {
5
18
  if (contract.orientation === 'directed') {
@@ -27,7 +40,10 @@ export function checkHeldKeys(facts, keys, report) {
27
40
  holders.set(key, fact);
28
41
  }
29
42
  }
30
- /** Check normalized Edge identity, noSelf, keys, exact-Class acyclicity and Core degrees. */
43
+ /**
44
+ * Check normalized Edge identity, noSelf, keys, and the acyclicity and Core degrees of each exact
45
+ * concrete Edge Class over its own Edges.
46
+ */
31
47
  export function checkCoreEdgeSet(set) {
32
48
  const identities = new Map();
33
49
  const slices = new Map(set.classes.map(({ key }) => [key, []]));
@@ -48,6 +64,10 @@ export function checkCoreEdgeSet(set) {
48
64
  }
49
65
  checkHeldKeys(set.edges, set.keys, (fact, previous) => set.report({ kind: 'key', edge: fact.edge, previous: previous.edge }));
50
66
  for (const { key, contract } of set.classes) {
67
+ // No Edge is of an abstract Class exactly, so it bounds no degree and closes no cycle of its
68
+ // own: each concrete descendant carries the counts it refines, over its own Edges.
69
+ if (contract.abstract)
70
+ continue;
51
71
  const slice = slices.get(key);
52
72
  if (contract.constraints.acyclic === true && hasDirectedCycle(slice))
53
73
  set.report({ kind: 'acyclic', class: key });
@@ -78,9 +98,10 @@ function checkDegrees(set, contract, slice) {
78
98
  }
79
99
  }
80
100
  /**
81
- * Every declared Core Node proves its own complete degree, so both bounds hold. A projected member
82
- * is only named by the Edges that reach it: the Domain may also create ordinary Edges of the same
83
- * Class against it, so a minimum is unprovable here and only the maximum is enforced.
101
+ * Every declared Core Node proves its own complete degree, so both bounds hold. A projected member,
102
+ * and the owning Domain, is only named by the Edges that reach it: the Domain may also create
103
+ * ordinary Edges of the same Class against it, so a minimum is unprovable here and only the maximum
104
+ * is enforced.
84
105
  */
85
106
  function degreeSubjects(nodes, slice) {
86
107
  const subjects = new Map(nodes.map((node) => [node.key, { ...node, minimum: true }]));
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The limits of the V1 language, each defined once: every check of a source Schema, a Bundle or a
3
+ * compiled closure reads its bound here. Every limit is inclusive: a count equal to it is
4
+ * accepted, one more is refused. `schema.limits` publishes this object, and each rule of
5
+ * `schema.RULES` and `schema.values.RULES` that enforces some of them names them in `limits`.
6
+ */
7
+ export declare const limits: Readonly<{
8
+ /** Bounds of the dependency graph of one Domain (`DV_DEPENDENCY_LIMITS`). */
9
+ dependency: Readonly<{
10
+ /** Direct dependencies one Domain declares. */
11
+ direct: 32;
12
+ /** Domains of a dependency closure, the Domain itself included. */
13
+ closure: 256;
14
+ /** Dependencies on the longest chain from a Domain: a direct dependency is at depth 1. */
15
+ depth: 32;
16
+ }>;
17
+ /** Bounds of one I-Regexp in a value schema (`VS_REGEX_PORTABLE`), as RFC 9485 §8 allows. */
18
+ regexp: Readonly<{
19
+ /** Code points of one source. */
20
+ maxCodePoints: 4096;
21
+ /** Groups nested in one another in one source. */
22
+ maxNesting: 32;
23
+ /** The greatest bound of a counted repetition `{n}` or `{n,m}`. */
24
+ maxRepeat: 1000;
25
+ /** States of one compiled machine; the counted-run form never expands and is not bounded. */
26
+ maxStates: 10000;
27
+ }>;
28
+ /**
29
+ * Bounds of the Policy language. A named Policy is measured once normalized and once each
30
+ * composition is expanded into the Policies it names; a check tree, the checks of a callable or
31
+ * of a Node Class rule, once normalized (`PL_BUDGET`). The structure bounds the others
32
+ * (`STRUCTURE_MAXIMUM`).
33
+ */
34
+ policy: Readonly<{
35
+ /**
36
+ * Levels of a Policy pattern: an Edge or sameNode is one, and each allOf, anyOf, exists and
37
+ * composition adds one.
38
+ */
39
+ patternDepth: 4;
40
+ /**
41
+ * Branches of a Policy's normal form: an anyOf adds those of its operands, an allOf multiplies
42
+ * them.
43
+ */
44
+ normalizedBranches: 8;
45
+ /** Nodes one exists declares. */
46
+ variablesPerExists: 4;
47
+ /** Graph predicates, Edges and sameNode equalities, of one normalized branch. */
48
+ edgePredicates: 6;
49
+ /** The greatest bound of a repeated Edge. */
50
+ repetition: 3;
51
+ /** Domains a Policy names: its own, and the origin of every Class and object it references. */
52
+ referencedDomains: 4;
53
+ /** Levels of a check tree: a check or sameNode is one, and each allOf and anyOf adds one. */
54
+ checkDepth: 4;
55
+ /** Branches of a check tree: an anyOf adds those of its operands, an allOf multiplies them. */
56
+ checkBranches: 8;
57
+ /** Checks and sameNode equalities of a check tree. */
58
+ checkLeaves: 8;
59
+ }>;
60
+ }>;
61
+ /** A limit by its key in `limits`, `<group>.<name>`: `policy.patternDepth`. */
62
+ export type LimitKey = {
63
+ readonly [Group in keyof typeof limits & string]: `${Group}.${keyof (typeof limits)[Group] & string}`;
64
+ }[keyof typeof limits & string];
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The limits of the V1 language, each defined once: every check of a source Schema, a Bundle or a
3
+ * compiled closure reads its bound here. Every limit is inclusive: a count equal to it is
4
+ * accepted, one more is refused. `schema.limits` publishes this object, and each rule of
5
+ * `schema.RULES` and `schema.values.RULES` that enforces some of them names them in `limits`.
6
+ */
7
+ export const limits = Object.freeze({
8
+ /** Bounds of the dependency graph of one Domain (`DV_DEPENDENCY_LIMITS`). */
9
+ dependency: Object.freeze({
10
+ /** Direct dependencies one Domain declares. */
11
+ direct: 32,
12
+ /** Domains of a dependency closure, the Domain itself included. */
13
+ closure: 256,
14
+ /** Dependencies on the longest chain from a Domain: a direct dependency is at depth 1. */
15
+ depth: 32,
16
+ }),
17
+ /** Bounds of one I-Regexp in a value schema (`VS_REGEX_PORTABLE`), as RFC 9485 §8 allows. */
18
+ regexp: Object.freeze({
19
+ /** Code points of one source. */
20
+ maxCodePoints: 4096,
21
+ /** Groups nested in one another in one source. */
22
+ maxNesting: 32,
23
+ /** The greatest bound of a counted repetition `{n}` or `{n,m}`. */
24
+ maxRepeat: 1000,
25
+ /** States of one compiled machine; the counted-run form never expands and is not bounded. */
26
+ maxStates: 10_000,
27
+ }),
28
+ /**
29
+ * Bounds of the Policy language. A named Policy is measured once normalized and once each
30
+ * composition is expanded into the Policies it names; a check tree, the checks of a callable or
31
+ * of a Node Class rule, once normalized (`PL_BUDGET`). The structure bounds the others
32
+ * (`STRUCTURE_MAXIMUM`).
33
+ */
34
+ policy: Object.freeze({
35
+ /**
36
+ * Levels of a Policy pattern: an Edge or sameNode is one, and each allOf, anyOf, exists and
37
+ * composition adds one.
38
+ */
39
+ patternDepth: 4,
40
+ /**
41
+ * Branches of a Policy's normal form: an anyOf adds those of its operands, an allOf multiplies
42
+ * them.
43
+ */
44
+ normalizedBranches: 8,
45
+ /** Nodes one exists declares. */
46
+ variablesPerExists: 4,
47
+ /** Graph predicates, Edges and sameNode equalities, of one normalized branch. */
48
+ edgePredicates: 6,
49
+ /** The greatest bound of a repeated Edge. */
50
+ repetition: 3,
51
+ /** Domains a Policy names: its own, and the origin of every Class and object it references. */
52
+ referencedDomains: 4,
53
+ /** Levels of a check tree: a check or sameNode is one, and each allOf and anyOf adds one. */
54
+ checkDepth: 4,
55
+ /** Branches of a check tree: an anyOf adds those of its operands, an allOf multiplies them. */
56
+ checkBranches: 8,
57
+ /** Checks and sameNode equalities of a check tree. */
58
+ checkLeaves: 8,
59
+ }),
60
+ });
@@ -1,28 +1,49 @@
1
1
  import type { PolicyRef } from '../../../addressing/index.js';
2
2
  import type { PolicyCheckExpression, PolicyExpression } from '../model/policy.js';
3
- export declare const MAX_PATTERN_DEPTH = 4;
4
- export declare const MAX_BRANCHES = 8;
5
- export declare const MAX_VARIABLES = 4;
6
- export declare const MAX_EDGES = 6;
7
- export declare const MAX_REPEAT = 3;
8
- export declare const MAX_DOMAINS = 4;
9
- export declare const MAX_CHECK_DEPTH = 4;
10
- export declare const MAX_CHECK_LEAVES = 8;
3
+ import { limits } from '../../limits.js';
4
+ /** A key of `limits.policy`. */
5
+ export type PolicyLimit = keyof typeof limits.policy;
6
+ /**
7
+ * Where a count of Policy branches stops: one past its limit. Each branch count is exact up to its
8
+ * limit plus one and reads as that once it is over, so expansion stays bounded.
9
+ */
11
10
  export declare const BRANCH_OVERFLOW: number;
12
- export interface PatternMeasure {
13
- readonly depth: number;
14
- readonly edgeCounts: readonly number[];
15
- readonly domains: ReadonlySet<string>;
11
+ /**
12
+ * What `PL_BUDGET` compares with `limits.policy` for one named Policy once normalized and each of
13
+ * its compositions expanded, every count under the key of its limit. The branch count stops one
14
+ * past its limit: it is exact up to that, and once it is over, `edgePredicates` reads the branches
15
+ * it counted only. The other counts are exact.
16
+ */
17
+ export interface PolicyMeasure {
18
+ /** Levels of the expanded pattern. */
19
+ readonly patternDepth: number;
20
+ /** Branches of its normal form, at most `limits.policy.normalizedBranches + 1`. */
21
+ readonly normalizedBranches: number;
22
+ /** The most graph predicates, Edges and sameNode equalities, of one branch. */
23
+ readonly edgePredicates: number;
24
+ /** Domains the expanded pattern names, its own included. */
25
+ readonly referencedDomains: number;
26
+ }
27
+ /**
28
+ * What `PL_BUDGET` compares with `limits.policy` for one check tree once normalized, every count
29
+ * under the key of its limit. The branch count stops one past its limit; the other counts are
30
+ * exact.
31
+ */
32
+ export interface CheckMeasure {
33
+ /** Levels of the tree. */
34
+ readonly checkDepth: number;
35
+ /** Branches of its normal form, at most `limits.policy.checkBranches + 1`. */
36
+ readonly checkBranches: number;
37
+ /** Checks and sameNode equalities of the tree. */
38
+ readonly checkLeaves: number;
16
39
  }
40
+ /** The limits of `limits.policy` a measure exceeds, in the order the measure lists them. */
41
+ export declare function exceededLimits<Measure extends PolicyMeasure | CheckMeasure>(measure: Measure): readonly (keyof Measure & PolicyLimit)[];
17
42
  /**
18
43
  * Measure named Policies by their expanded branches, depth and referenced Domains. A Policy has no
19
44
  * measure when it is not normalized, when its expansion reaches itself, or when a composition names
20
45
  * a Policy `isLocal` refuses or one without a measure.
21
46
  */
22
- export declare function policyMeasurer(origin: string, normalized: ReadonlyMap<string, PolicyExpression>, isLocal: (ref: PolicyRef) => boolean): (name: string) => PatternMeasure | undefined;
23
- export interface CheckMeasure {
24
- readonly depth: number;
25
- readonly branches: number;
26
- readonly leaves: number;
27
- }
47
+ export declare function policyMeasurer(origin: string, normalized: ReadonlyMap<string, PolicyExpression>, isLocal: (ref: PolicyRef) => boolean): (name: string) => PolicyMeasure | undefined;
48
+ /** Measure one normalized check tree. */
28
49
  export declare function measureChecks(expression: PolicyCheckExpression): CheckMeasure;
@@ -1,13 +1,15 @@
1
+ import { limits } from '../../limits.js';
1
2
  import { policyObjectOrigin, policyVariableClass } from '../model/policy.js';
2
- export const MAX_PATTERN_DEPTH = 4;
3
- export const MAX_BRANCHES = 8;
4
- export const MAX_VARIABLES = 4;
5
- export const MAX_EDGES = 6;
6
- export const MAX_REPEAT = 3;
7
- export const MAX_DOMAINS = 4;
8
- export const MAX_CHECK_DEPTH = 4;
9
- export const MAX_CHECK_LEAVES = 8;
10
- export const BRANCH_OVERFLOW = MAX_BRANCHES + 1;
3
+ /**
4
+ * Where a count of Policy branches stops: one past its limit. Each branch count is exact up to its
5
+ * limit plus one and reads as that once it is over, so expansion stays bounded.
6
+ */
7
+ export const BRANCH_OVERFLOW = limits.policy.normalizedBranches + 1;
8
+ const CHECK_BRANCH_OVERFLOW = limits.policy.checkBranches + 1;
9
+ /** The limits of `limits.policy` a measure exceeds, in the order the measure lists them. */
10
+ export function exceededLimits(measure) {
11
+ return Object.keys(measure).filter((limit) => measure[limit] > limits.policy[limit]);
12
+ }
11
13
  /**
12
14
  * Measure named Policies by their expanded branches, depth and referenced Domains. A Policy has no
13
15
  * measure when it is not normalized, when its expansion reaches itself, or when a composition names
@@ -48,7 +50,17 @@ export function policyMeasurer(origin, normalized, isLocal) {
48
50
  return undefined;
49
51
  return combineMeasures('allOf' in declaration ? 'allOf' : 'anyOf', children);
50
52
  };
51
- return (name) => measurePolicy(name, new Set());
53
+ return (name) => {
54
+ const measure = measurePolicy(name, new Set());
55
+ return measure === undefined
56
+ ? undefined
57
+ : {
58
+ patternDepth: measure.depth,
59
+ normalizedBranches: measure.edgeCounts.length,
60
+ edgePredicates: Math.max(0, ...measure.edgeCounts),
61
+ referencedDomains: measure.domains.size,
62
+ };
63
+ };
52
64
  }
53
65
  function measurePattern(pattern, origin) {
54
66
  if ('sameNode' in pattern)
@@ -111,19 +123,17 @@ function combineMeasures(kind, children) {
111
123
  }
112
124
  return { depth, edgeCounts, domains };
113
125
  }
126
+ /** Measure one normalized check tree. */
114
127
  export function measureChecks(expression) {
115
128
  if ('check' in expression || 'sameNode' in expression)
116
- return { depth: 1, branches: 1, leaves: 1 };
129
+ return { checkDepth: 1, checkBranches: 1, checkLeaves: 1 };
117
130
  const children = ('allOf' in expression ? expression.allOf : expression.anyOf).map(measureChecks);
118
- const branches = 'allOf' in expression
119
- ? children.reduce((value, child) => saturatingProduct(value, child.branches), 1)
120
- : children.reduce((value, child) => Math.min(BRANCH_OVERFLOW, value + child.branches), 0);
131
+ const checkBranches = 'allOf' in expression
132
+ ? children.reduce((value, child) => Math.min(CHECK_BRANCH_OVERFLOW, value * child.checkBranches), 1)
133
+ : children.reduce((value, child) => Math.min(CHECK_BRANCH_OVERFLOW, value + child.checkBranches), 0);
121
134
  return {
122
- depth: 1 + Math.max(0, ...children.map((child) => child.depth)),
123
- branches,
124
- leaves: children.reduce((value, child) => value + child.leaves, 0),
135
+ checkDepth: 1 + Math.max(0, ...children.map((child) => child.checkDepth)),
136
+ checkBranches,
137
+ checkLeaves: children.reduce((value, child) => value + child.checkLeaves, 0),
125
138
  };
126
139
  }
127
- function saturatingProduct(left, right) {
128
- return Math.min(BRANCH_OVERFLOW, left * right);
129
- }
@@ -1,5 +1,6 @@
1
+ import { limits } from '../../limits.js';
1
2
  import { policyObjectKey } from '../model/policy.js';
2
- import { MAX_BRANCHES, measureChecks } from './budgets.js';
3
+ import { measureChecks } from './budgets.js';
3
4
  /** Names a callable check object. Every leaf over one object sees the same Node at runtime. */
4
5
  function checkObjectName(object) {
5
6
  if (object.kind === 'self')
@@ -36,7 +37,7 @@ export function admittedCheckClasses(typing) {
36
37
  * check budget is already refused, so it is not expanded.
37
38
  */
38
39
  export function neverAdmittedClasses(expression, leaves) {
39
- if (measureChecks(expression).branches > MAX_BRANCHES)
40
+ if (measureChecks(expression).checkBranches > limits.policy.checkBranches)
40
41
  return [];
41
42
  const accepted = new Map();
42
43
  for (const [leaf, classes] of leaves)
@@ -1,7 +1,7 @@
1
1
  import { Key } from '../../../addressing/index.js';
2
2
  import { definitionRefIndex } from '../../../addressing/ordering.js';
3
+ import { limits } from '../../limits.js';
3
4
  import { branchesOf, branchProduct, capBranches, targetMode, termKey, UnionFind, } from './branches.js';
4
- import { MAX_REPEAT } from './budgets.js';
5
5
  import { admittedCheckClasses } from './checks.js';
6
6
  export class PolicyTyping {
7
7
  scope;
@@ -12,7 +12,8 @@ export class PolicyTyping {
12
12
  // independent of the reaching path up to the opaque term-namespace labels, which only ever
13
13
  // disambiguate variables within one branch and leave targetMode and branch typing unchanged.
14
14
  // Two occurrences of one Policy in a branch share labels only when a Policy reaches it by two
15
- // paths; for that to merge distinct variables, the Policy expands deeper than MAX_PATTERN_DEPTH.
15
+ // paths; for that to merge distinct variables, the Policy expands deeper than
16
+ // `limits.policy.patternDepth`.
16
17
  #expansions = new Map();
17
18
  constructor(scope) {
18
19
  this.scope = scope;
@@ -269,7 +270,7 @@ function variantsForEdge(use, typing) {
269
270
  !Number.isInteger(bounds.max) ||
270
271
  bounds.min < 0 ||
271
272
  bounds.max < bounds.min ||
272
- bounds.max > MAX_REPEAT) {
273
+ bounds.max > limits.policy.repetition) {
273
274
  return [];
274
275
  }
275
276
  const sourceKey = termKey(edge.source, use.namespace);
@@ -1,3 +1,4 @@
1
+ import type { LimitKey } from '../limits.js';
1
2
  import type { DiagnosticCode } from './diagnostic.js';
2
3
  /**
3
4
  * What a rule reads to decide. `schema`: a value schema alone, with its place in the declaration
@@ -18,6 +19,8 @@ export interface ValueRule {
18
19
  readonly positions: readonly [string, ...string[]];
19
20
  /** The rule, in one plain sentence. */
20
21
  readonly statement: string;
22
+ /** The limits of `schema.limits` the rule enforces, by key, where it enforces some. */
23
+ readonly limits?: readonly [LimitKey, ...LimitKey[]];
21
24
  }
22
25
  /**
23
26
  * Every value diagnostic code with the rule it reports. A code added to the language does not
@@ -92,6 +92,7 @@ export const VALUE_RULES = frozen({
92
92
  stage: 'schema',
93
93
  positions: ['{/path*}/pattern', '{/path*}/fullMatch', '{/path*}/patternProperties/{pattern}'],
94
94
  statement: 'Every pattern, fullMatch and patternProperties name is an RFC 9485 I-Regexp within the regular-expression limits.',
95
+ limits: ['regexp.maxCodePoints', 'regexp.maxNesting', 'regexp.maxRepeat', 'regexp.maxStates'],
95
96
  },
96
97
  VS_RESOURCE_ID: {
97
98
  stage: 'schema',
@@ -110,7 +111,10 @@ export const VALUE_RULES = frozen({
110
111
  },
111
112
  });
112
113
  function frozen(rules) {
113
- for (const rule of Object.values(rules))
114
+ for (const rule of Object.values(rules)) {
114
115
  Object.freeze(Object.freeze(rule).positions);
116
+ if (rule.limits !== undefined)
117
+ Object.freeze(rule.limits);
118
+ }
115
119
  return Object.freeze(rules);
116
120
  }
@@ -1,8 +1,7 @@
1
+ import { limits } from '../../limits.js';
1
2
  import { categoryRanges, complement, mergeRanges } from './unicode.js';
2
3
  const CATEGORY = /^(?:L[lmotu]?|M[cen]?|N[dlo]?|P[cdefios]?|Z[lps]?|S[ckmo]?|C[cfno]?)$/u;
3
- const MAX_CODE_POINTS = 4096;
4
- const MAX_NESTING = 32;
5
- const MAX_REPEAT = 1000;
4
+ const { maxCodePoints, maxNesting, maxRepeat } = limits.regexp;
6
5
  const DOT_RANGES = [
7
6
  [0, 9],
8
7
  [11, 12],
@@ -14,7 +13,7 @@ export function parseIRegexp(source) {
14
13
  if (!isUnicodeScalarString(source) || source === '[^]')
15
14
  return undefined;
16
15
  const characters = Array.from(source);
17
- if (characters.length > MAX_CODE_POINTS)
16
+ if (characters.length > maxCodePoints)
18
17
  return undefined;
19
18
  const parser = new Parser(characters);
20
19
  const expression = parser.expression(undefined, 0);
@@ -30,7 +29,7 @@ class Parser {
30
29
  return this.index === this.characters.length;
31
30
  }
32
31
  expression(stop, depth) {
33
- if (depth > MAX_NESTING)
32
+ if (depth > maxNesting)
34
33
  return undefined;
35
34
  const alternatives = [];
36
35
  const first = this.sequence(stop, depth);
@@ -112,14 +111,14 @@ class Parser {
112
111
  quantifier() {
113
112
  this.index++;
114
113
  const minimum = this.integer();
115
- if (minimum === undefined || minimum > MAX_REPEAT)
114
+ if (minimum === undefined || minimum > maxRepeat)
116
115
  return undefined;
117
116
  let maximum = minimum;
118
117
  if (this.peek() === ',') {
119
118
  this.index++;
120
119
  maximum = this.integer();
121
120
  }
122
- if ((maximum !== undefined && (maximum > MAX_REPEAT || maximum < minimum)) ||
121
+ if ((maximum !== undefined && (maximum > maxRepeat || maximum < minimum)) ||
123
122
  this.peek() !== '}') {
124
123
  return undefined;
125
124
  }
@@ -1,16 +1,9 @@
1
1
  import type { Pattern, RunPattern } from '../compile/model.js';
2
2
  import type { Expression } from './ast.js';
3
3
  /**
4
- * The most states the machine of one I-Regexp may have (`limits.regexp.maxStates`), an
5
- * implementation limit as RFC 9485 §8 allows. Counted repetitions are expanded copy by copy, so the
6
- * parser's bounds on length, nesting and counts alone admit machines of billions of states. The
7
- * counted-run form never expands, so it is not subject to this budget.
8
- */
9
- export declare const MAX_STATES = 10000;
10
- /**
11
- * Thrown when an I-Regexp source compiles to a machine of more than `MAX_STATES` states. A short
12
- * branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and cost seconds per
13
- * validated value (P1-I-01); it is refused before any state is built.
4
+ * Thrown when an I-Regexp source compiles to a machine of more than `limits.regexp.maxStates`
5
+ * states. A short branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and
6
+ * cost seconds per validated value (P1-I-01); it is refused before any state is built.
14
7
  */
15
8
  export declare class PatternComplexityError extends RangeError {
16
9
  readonly limit: number;
@@ -20,7 +13,8 @@ export declare class PatternComplexityError extends RangeError {
20
13
  export declare function compilePattern(source: string): Pattern | RunPattern;
21
14
  /**
22
15
  * Whether a parsed I-Regexp compiles within the state budget: as counted runs, which never expand,
23
- * or as a machine of at most `MAX_STATES` states, counted from the AST before anything is built.
16
+ * or as a machine of at most `limits.regexp.maxStates` states, counted from the AST before anything
17
+ * is built.
24
18
  */
25
19
  export declare function withinStateBudget(expression: Expression): boolean;
26
20
  /**
@@ -1,19 +1,20 @@
1
+ import { limits } from '../../limits.js';
1
2
  import { parseIRegexp } from './ast.js';
2
3
  /**
3
- * The most states the machine of one I-Regexp may have (`limits.regexp.maxStates`), an
4
- * implementation limit as RFC 9485 §8 allows. Counted repetitions are expanded copy by copy, so the
5
- * parser's bounds on length, nesting and counts alone admit machines of billions of states. The
6
- * counted-run form never expands, so it is not subject to this budget.
4
+ * The most states the machine of one I-Regexp may have, an implementation limit as RFC 9485 §8
5
+ * allows. Counted repetitions are expanded copy by copy, so the parser's bounds on length, nesting
6
+ * and counts alone admit machines of billions of states. The counted-run form never expands, so it
7
+ * is not subject to this budget.
7
8
  */
8
- export const MAX_STATES = 10_000;
9
+ const { maxStates } = limits.regexp;
9
10
  /**
10
- * Thrown when an I-Regexp source compiles to a machine of more than `MAX_STATES` states. A short
11
- * branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and cost seconds per
12
- * validated value (P1-I-01); it is refused before any state is built.
11
+ * Thrown when an I-Regexp source compiles to a machine of more than `limits.regexp.maxStates`
12
+ * states. A short branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and
13
+ * cost seconds per validated value (P1-I-01); it is refused before any state is built.
13
14
  */
14
15
  export class PatternComplexityError extends RangeError {
15
16
  limit;
16
- constructor(limit = MAX_STATES) {
17
+ constructor(limit = maxStates) {
17
18
  super(`Cannot compile an I-Regexp machine of more than ${limit} states.`);
18
19
  this.limit = limit;
19
20
  this.name = 'PatternComplexityError';
@@ -27,7 +28,7 @@ export function compilePattern(source) {
27
28
  const runs = scalarRuns(expression);
28
29
  if (runs !== undefined)
29
30
  return Object.freeze({ source, runs });
30
- if (states(expression) > MAX_STATES)
31
+ if (states(expression) > maxStates)
31
32
  throw new PatternComplexityError();
32
33
  const builder = new Builder();
33
34
  const fragment = builder.expression(expression);
@@ -40,10 +41,11 @@ export function compilePattern(source) {
40
41
  }
41
42
  /**
42
43
  * Whether a parsed I-Regexp compiles within the state budget: as counted runs, which never expand,
43
- * or as a machine of at most `MAX_STATES` states, counted from the AST before anything is built.
44
+ * or as a machine of at most `limits.regexp.maxStates` states, counted from the AST before anything
45
+ * is built.
44
46
  */
45
47
  export function withinStateBudget(expression) {
46
- return scalarRuns(expression) !== undefined || states(expression) <= MAX_STATES;
48
+ return scalarRuns(expression) !== undefined || states(expression) <= maxStates;
47
49
  }
48
50
  /**
49
51
  * The number of states `Builder` builds for an expression, computed from the AST alone: `states`