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

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 (152) hide show
  1. package/dist/v1/addressing/definition-ref.js +21 -37
  2. package/dist/v1/addressing/key.js +2 -1
  3. package/dist/v1/addressing/ordering.d.ts +2 -0
  4. package/dist/v1/addressing/ordering.js +4 -0
  5. package/dist/v1/addressing/patterns.d.ts +1 -1
  6. package/dist/v1/addressing/patterns.js +2 -6
  7. package/dist/v1/builder/class/authoring.js +5 -28
  8. package/dist/v1/builder/class/property.d.ts +3 -9
  9. package/dist/v1/builder/class/property.js +1 -0
  10. package/dist/v1/builder/lowering/class.js +5 -1
  11. package/dist/v1/builder/lowering/policy.js +1 -0
  12. package/dist/v1/builder/lowering/schema/context.js +2 -4
  13. package/dist/v1/builder/lowering/value/path.js +2 -4
  14. package/dist/v1/builder/lowering/value/zod.js +1 -3
  15. package/dist/v1/builder/policy/authoring.js +3 -2
  16. package/dist/v1/builder/schema/define.d.ts +0 -1
  17. package/dist/v1/builder/schema/diagnostic.d.ts +2 -1
  18. package/dist/v1/builder/schema/diagnostic.js +5 -10
  19. package/dist/v1/builder/schema/projection.d.ts +1 -1
  20. package/dist/v1/builder/value/full-match.d.ts +0 -1
  21. package/dist/v1/builder/value/full-match.js +0 -1
  22. package/dist/v1/bundle/codec.js +4 -6
  23. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  24. package/dist/v1/bundle/diagnostic.js +9 -7
  25. package/dist/v1/bundle/structure.js +4 -6
  26. package/dist/v1/compiled/admission/diagnostic.d.ts +1 -1
  27. package/dist/v1/compiled/admission/diagnostic.js +1 -3
  28. package/dist/v1/compiled/admission/revision.d.ts +9 -1
  29. package/dist/v1/compiled/admission/revision.js +6 -3
  30. package/dist/v1/compiled/admission/semantics/class.d.ts +4 -1
  31. package/dist/v1/compiled/admission/semantics/class.js +4 -8
  32. package/dist/v1/compiled/admission/semantics/core.d.ts +1 -24
  33. package/dist/v1/compiled/admission/semantics/core.js +54 -34
  34. package/dist/v1/compiled/admission/semantics/edge.d.ts +2 -4
  35. package/dist/v1/compiled/admission/semantics/edge.js +6 -65
  36. package/dist/v1/compiled/admission/semantics/policy/callable.js +0 -2
  37. package/dist/v1/compiled/admission/semantics/policy/contracts.d.ts +5 -20
  38. package/dist/v1/compiled/admission/semantics/policy/contracts.js +8 -21
  39. package/dist/v1/compiled/admission/semantics/policy/parity.js +13 -22
  40. package/dist/v1/compiled/admission/semantics/policy/pattern.d.ts +4 -1
  41. package/dist/v1/compiled/admission/semantics/policy/pattern.js +4 -16
  42. package/dist/v1/compiled/admission/semantics/policy/typing.d.ts +2 -24
  43. package/dist/v1/compiled/admission/semantics/policy/typing.js +19 -201
  44. package/dist/v1/compiled/compiler/compile.js +6 -8
  45. package/dist/v1/compiled/diagnostic.js +2 -3
  46. package/dist/v1/compiled/loader/load.d.ts +3 -2
  47. package/dist/v1/compiled/loader/load.js +10 -44
  48. package/dist/v1/domain/create.js +3 -5
  49. package/dist/v1/domain/namespace/member-collection.js +4 -6
  50. package/dist/v1/domain/validation/create.d.ts +4 -2
  51. package/dist/v1/domain/validation/create.js +21 -69
  52. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  53. package/dist/v1/language/class/inheritance.d.ts +18 -0
  54. package/dist/v1/language/class/inheritance.js +73 -0
  55. package/dist/v1/language/class/model/class.d.ts +4 -4
  56. package/dist/v1/language/class/model/edge.d.ts +2 -2
  57. package/dist/v1/language/class/model/property.d.ts +16 -7
  58. package/dist/v1/language/class/property-values.js +2 -4
  59. package/dist/v1/language/core/facts.d.ts +72 -0
  60. package/dist/v1/language/core/facts.js +95 -0
  61. package/dist/v1/language/definition/metadata.d.ts +5 -0
  62. package/dist/v1/language/definition/metadata.js +7 -0
  63. package/dist/v1/language/policy/analysis/branches.d.ts +8 -0
  64. package/dist/v1/language/policy/analysis/branches.js +2 -1
  65. package/dist/v1/language/policy/analysis/budgets.d.ts +13 -1
  66. package/dist/v1/language/policy/analysis/budgets.js +104 -0
  67. package/dist/v1/language/policy/analysis/typing.d.ts +48 -0
  68. package/dist/v1/language/policy/analysis/typing.js +338 -0
  69. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  70. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  71. package/dist/v1/language/validation/diagnostic.d.ts +15 -1
  72. package/dist/v1/language/validation/diagnostic.js +21 -0
  73. package/dist/v1/language/validation/rule.d.ts +26 -0
  74. package/dist/v1/language/validation/rule.js +116 -0
  75. package/dist/v1/language/value/compile/fields.js +4 -8
  76. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  77. package/dist/v1/language/value/profile/rules.js +17 -43
  78. package/dist/v1/language/value/profile/validate.js +1 -10
  79. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  80. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  81. package/dist/v1/language/value/reference/recursion.js +2 -22
  82. package/dist/v1/language/value/reference/registry.index.js +2 -1
  83. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  84. package/dist/v1/language/value/syntax/keywords.js +4 -20
  85. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  86. package/dist/v1/language/value/syntax/pointer.d.ts +2 -0
  87. package/dist/v1/language/value/syntax/pointer.js +5 -2
  88. package/dist/v1/language/value/syntax/walk.d.ts +2 -0
  89. package/dist/v1/language/value/syntax/walk.js +19 -20
  90. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  91. package/dist/v1/schema/analysis/class.js +33 -15
  92. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  93. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  94. package/dist/v1/schema/compatibility/diff.js +2 -4
  95. package/dist/v1/schema/compatibility/meaning/footprint.js +16 -50
  96. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  97. package/dist/v1/schema/compatibility/structure/requirements.js +26 -58
  98. package/dist/v1/schema/compatibility/walk.d.ts +17 -0
  99. package/dist/v1/schema/compatibility/walk.js +41 -0
  100. package/dist/v1/schema/diagnostic.d.ts +17 -3
  101. package/dist/v1/schema/diagnostic.js +14 -11
  102. package/dist/v1/schema/index.d.ts +3 -1
  103. package/dist/v1/schema/index.js +1 -0
  104. package/dist/v1/schema/model/schema.d.ts +19 -1
  105. package/dist/v1/schema/normalization/domain.js +1 -0
  106. package/dist/v1/schema/resolution/closure.js +2 -1
  107. package/dist/v1/schema/resolution/context.js +6 -6
  108. package/dist/v1/schema/rule.d.ts +29 -0
  109. package/dist/v1/schema/rule.js +415 -0
  110. package/dist/v1/schema/validation/callable.d.ts +4 -1
  111. package/dist/v1/schema/validation/callable.js +13 -6
  112. package/dist/v1/schema/validation/class.js +46 -96
  113. package/dist/v1/schema/validation/core.js +68 -202
  114. package/dist/v1/schema/validation/domain.js +1 -10
  115. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  116. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  117. package/dist/v1/schema/validation/domain.structure.d.ts +4018 -0
  118. package/dist/v1/schema/validation/domain.structure.js +343 -155
  119. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  120. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  121. package/dist/v1/schema/validation/policy/budgets.js +5 -112
  122. package/dist/v1/schema/validation/policy/context.d.ts +5 -3
  123. package/dist/v1/schema/validation/policy/context.js +5 -3
  124. package/dist/v1/schema/validation/policy/objects.d.ts +1 -1
  125. package/dist/v1/schema/validation/policy/objects.js +6 -14
  126. package/dist/v1/schema/validation/policy/references.js +10 -27
  127. package/dist/v1/schema/validation/policy/typing.d.ts +3 -18
  128. package/dist/v1/schema/validation/policy/typing.js +22 -356
  129. package/dist/v1/schema/validation/policy/uses.d.ts +1 -1
  130. package/dist/v1/schema/validation/policy/uses.js +9 -25
  131. package/dist/v1/schema/validation/policy/validate.js +3 -2
  132. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  133. package/dist/v1/schema/validation/value-api.js +12 -3
  134. package/dist/v1/schema/validation/value.js +8 -10
  135. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  136. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  137. package/dist/v1/schema/validation/view.js +4 -8
  138. package/package.json +3 -1
  139. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +0 -5
  140. package/dist/v1/compiled/admission/semantics/cardinality.js +0 -128
  141. package/dist/v1/compiled/admission/semantics/policy/equality.d.ts +0 -8
  142. package/dist/v1/compiled/admission/semantics/policy/equality.js +0 -27
  143. package/dist/v1/compiled/admission/semantics/policy/measure.d.ts +0 -9
  144. package/dist/v1/compiled/admission/semantics/policy/measure.js +0 -81
  145. package/dist/v1/compiled/admission/semantics/policy/variants.d.ts +0 -10
  146. package/dist/v1/compiled/admission/semantics/policy/variants.js +0 -65
  147. package/dist/v1/language/class/index.d.ts +0 -8
  148. package/dist/v1/language/class/index.js +0 -1
  149. package/dist/v1/language/core/index.d.ts +0 -2
  150. package/dist/v1/language/core/index.js +0 -1
  151. package/dist/v1/language/value/index.d.ts +0 -11
  152. package/dist/v1/language/value/index.js +0 -8
@@ -36,3 +36,76 @@ export function inheritanceOf(declaration) {
36
36
  export function admitsForeignSubclass(declaration) {
37
37
  return inheritanceOf(declaration) === 'open';
38
38
  }
39
+ /**
40
+ * Whether `child` safely refines its parent Edge contract `parent`: same orientation, at least the
41
+ * parent's constraints, and endpoints whose accepted Classes `satisfies` one the paired parent
42
+ * endpoint accepts, within its count. Undirected endpoints may pair in either order.
43
+ */
44
+ export function edgeContractRefines(child, parent, satisfies) {
45
+ if (isGraphRoot(parent))
46
+ return true;
47
+ if (child.orientation !== parent.orientation)
48
+ return false;
49
+ if (parent.constraints.noSelf === true && child.constraints.noSelf !== true)
50
+ return false;
51
+ if (parent.constraints.acyclic === true && child.constraints.acyclic !== true)
52
+ return false;
53
+ const endpointRefines = (childEndpoint, parentEndpoint) => childEndpoint.accepts.every((accepted) => satisfies(accepted, parentEndpoint.accepts));
54
+ if (child.orientation === 'directed' && parent.orientation === 'directed') {
55
+ return (endpointRefines(child.endpoints.source, parent.endpoints.source) &&
56
+ countRefines(child.endpoints.source.outgoing, parent.endpoints.source.outgoing) &&
57
+ endpointRefines(child.endpoints.target, parent.endpoints.target) &&
58
+ countRefines(child.endpoints.target.incoming, parent.endpoints.target.incoming));
59
+ }
60
+ if (child.orientation !== 'undirected' || parent.orientation !== 'undirected')
61
+ return false;
62
+ const refines = (childIndex, parentIndex) => endpointRefines(child.endpoints[childIndex], parent.endpoints[parentIndex]) &&
63
+ countRefines(child.endpoints[childIndex].incident, parent.endpoints[parentIndex].incident);
64
+ const direct = refines(0, 0) && refines(1, 1);
65
+ if (direct)
66
+ return true;
67
+ return refines(0, 1) && refines(1, 0);
68
+ }
69
+ function countRefines(child, parent) {
70
+ const [childMinimum, childMaximum] = countBounds(child);
71
+ const [parentMinimum, parentMaximum] = countBounds(parent);
72
+ return childMinimum >= parentMinimum && childMaximum <= parentMaximum;
73
+ }
74
+ /** Interpret an Edge count as inclusive degree bounds. */
75
+ export function countBounds(value) {
76
+ switch (value) {
77
+ case '0..*':
78
+ return [0, Number.POSITIVE_INFINITY];
79
+ case '1..*':
80
+ return [1, Number.POSITIVE_INFINITY];
81
+ case '0..1':
82
+ return [0, 1];
83
+ case '1':
84
+ return [1, 1];
85
+ }
86
+ }
87
+ /** Whether Edges of one Class, read from source to target, close a cycle (`acyclic` forbids it). */
88
+ export function hasDirectedCycle(edges) {
89
+ const outgoing = new Map();
90
+ for (const edge of edges) {
91
+ const targets = outgoing.get(edge.source.key) ?? [];
92
+ targets.push(edge.target.key);
93
+ outgoing.set(edge.source.key, targets);
94
+ }
95
+ const visiting = new Set();
96
+ const visited = new Set();
97
+ const visit = (key) => {
98
+ if (visiting.has(key))
99
+ return true;
100
+ if (visited.has(key))
101
+ return false;
102
+ visiting.add(key);
103
+ for (const target of outgoing.get(key) ?? [])
104
+ if (visit(target))
105
+ return true;
106
+ visiting.delete(key);
107
+ visited.add(key);
108
+ return false;
109
+ };
110
+ return [...outgoing.keys()].some(visit);
111
+ }
@@ -3,7 +3,7 @@ import type { MethodRecord } from '../../callable/model/callable.js';
3
3
  import type { ClassPolicy, NodeClassPolicy } from '../../policy/model/policy.js';
4
4
  import type { DataFacet } from './data.js';
5
5
  import type { EdgeContract } from './edge.js';
6
- import type { PropertyFacet } from './property.js';
6
+ import type { EdgePropertyDefinition, NodePropertyDefinition, PropertyFacet } from './property.js';
7
7
  export type ClassKind = 'node' | 'edge';
8
8
  /**
9
9
  * Which Classes may extend a Class: `'open'`, those of its Domain and of every Domain that depends
@@ -30,7 +30,7 @@ export type NodeUniqueTuple = readonly [PropertyKey, ...PropertyKey[]];
30
30
  * backend announces it to enforce Node keys, so a tuple is never ignored where it is not.
31
31
  */
32
32
  export type NodeUniqueForm = readonly ['property'];
33
- export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet, DataFacet {
33
+ export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet<NodePropertyDefinition>, DataFacet {
34
34
  readonly kind: 'node';
35
35
  readonly abstract: boolean;
36
36
  /** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
@@ -44,9 +44,9 @@ export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyF
44
44
  * The tuples no two Nodes of this Class and of its active concrete descendants share while every
45
45
  * Property is present; absent, none. A descendant is covered without declaring them again.
46
46
  */
47
- readonly unique?: readonly NodeUniqueTuple[];
47
+ readonly unique?: readonly [NodeUniqueTuple, ...NodeUniqueTuple[]];
48
48
  }
49
- export type EdgeClassDefinition = DefinitionBase<ClassRef> & PropertyFacet & EdgeContract & {
49
+ export type EdgeClassDefinition = DefinitionBase<ClassRef> & PropertyFacet<EdgePropertyDefinition> & EdgeContract & {
50
50
  readonly kind: 'edge';
51
51
  readonly abstract: boolean;
52
52
  /** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
@@ -8,7 +8,7 @@ export type EdgeOrientation = 'directed' | 'undirected';
8
8
  export type EdgeLifetime = 'restrict' | 'detach';
9
9
  export interface EdgeEndpointBase {
10
10
  readonly role: string;
11
- readonly accepts: readonly ClassRef[];
11
+ readonly accepts: readonly [ClassRef, ...ClassRef[]];
12
12
  /** Declared only when authored; otherwise derived (`edgeLifetimes`). */
13
13
  readonly onDelete?: EdgeLifetime;
14
14
  }
@@ -56,7 +56,7 @@ export interface DirectedEdgeContract {
56
56
  * The tuples no two Edges of this Class and of its active concrete descendants share. Absent
57
57
  * when the Class declares none, so a Class without one keeps its revision; never empty.
58
58
  */
59
- readonly unique?: readonly EdgeUniqueTuple[];
59
+ readonly unique?: readonly [EdgeUniqueTuple, ...EdgeUniqueTuple[]];
60
60
  }
61
61
  export interface UndirectedEdgeContract {
62
62
  readonly orientation: 'undirected';
@@ -1,20 +1,29 @@
1
+ import type { StateTopology } from '../../state/index.js';
2
+ import type { JsonSchema } from '../../value/model/schema.js';
1
3
  export type AutoEvent = 'create' | 'update';
2
4
  export type AutoSet = 'instant';
3
5
  /** `create`: written only by node or edge creation. `once`: written while absent, never unset. */
4
6
  export type PropertyImmutability = 'create' | 'once';
5
7
  export interface PropertyAuto {
6
- readonly on: readonly AutoEvent[];
8
+ readonly on: readonly [AutoEvent, ...AutoEvent[]];
7
9
  readonly set: AutoSet;
8
10
  }
9
- export interface PropertyDefinition {
10
- readonly schema: import('../../value/model/schema.js').JsonSchema;
11
+ /** A Property of a Node Class, which may be a State Property. */
12
+ export interface NodePropertyDefinition {
13
+ readonly schema: JsonSchema;
11
14
  readonly required: boolean;
12
15
  readonly auto?: PropertyAuto;
13
16
  readonly immutable?: PropertyImmutability;
14
17
  readonly description?: string;
15
- readonly state?: import('../../state/index.js').StateTopology;
18
+ readonly state?: StateTopology;
16
19
  }
17
- export type PropertyRecord = Readonly<Record<string, PropertyDefinition>>;
18
- export interface PropertyFacet {
19
- readonly properties: PropertyRecord;
20
+ /** A Property of an Edge Class: an Edge has no State Property. */
21
+ export interface EdgePropertyDefinition extends Omit<NodePropertyDefinition, 'state'> {
22
+ readonly state?: never;
23
+ }
24
+ /** A Property of a Class of either kind. */
25
+ export type PropertyDefinition = NodePropertyDefinition | EdgePropertyDefinition;
26
+ export type PropertyRecord<Definition extends PropertyDefinition = PropertyDefinition> = Readonly<Record<string, Definition>>;
27
+ export interface PropertyFacet<Definition extends PropertyDefinition = PropertyDefinition> {
28
+ readonly properties: PropertyRecord<Definition>;
20
29
  }
@@ -1,6 +1,7 @@
1
1
  import { acceptPropertyRoot } from '../../../value/accept.js';
2
2
  import { isImmutableSnapshot } from '../../../value/immutable.optimization.js';
3
3
  import { PropertyKey as admitPropertyKey } from '../../addressing/index.js';
4
+ import { compareCodeUnits } from '../../addressing/ordering.js';
4
5
  export function acceptPropertyValues(input, pointer = '') {
5
6
  const admitted = acceptPropertyRoot(input);
6
7
  if (admitted === null || typeof admitted !== 'object' || Array.isArray(admitted)) {
@@ -10,7 +11,7 @@ export function acceptPropertyValues(input, pointer = '') {
10
11
  }
11
12
  export function createPropertyValues(entries) {
12
13
  const output = Object.create(null);
13
- for (const [key, value] of [...entries].sort(([left], [right]) => compare(left, right))) {
14
+ for (const [key, value] of [...entries].sort(([left], [right]) => compareCodeUnits(left, right))) {
14
15
  Object.defineProperty(output, key, { enumerable: true, value: freeze(value) });
15
16
  }
16
17
  return Object.freeze(output);
@@ -28,6 +29,3 @@ function freeze(value) {
28
29
  freeze(member);
29
30
  return Object.freeze(value);
30
31
  }
31
- function compare(left, right) {
32
- return left < right ? -1 : left > right ? 1 : 0;
33
- }
@@ -0,0 +1,72 @@
1
+ import type { ClassRef } from '../../addressing/index.js';
2
+ import type { EdgeContract, EdgeCount, EdgeEndpointBase, IncidentEndpoint } from '../class/model/edge.js';
3
+ /** One endpoint of a Core Edge: a Core Node, a projected member or the owning Domain. */
4
+ export interface CoreFactEndpoint {
5
+ readonly key: string;
6
+ /** Set on a projected member: its Kernel Class, and where a breach of its degree is reported. */
7
+ readonly member?: {
8
+ readonly class: ClassRef;
9
+ readonly pointer: string;
10
+ };
11
+ }
12
+ /** One Core Edge whose endpoints satisfy its exact Class. */
13
+ export interface CoreFactEdge<Edge> {
14
+ readonly edge: Edge;
15
+ /** The exact Class's key, as `CoreEdgeSet.classes` names it. */
16
+ readonly class: string;
17
+ readonly contract: EdgeContract;
18
+ readonly source: CoreFactEndpoint;
19
+ readonly target: CoreFactEndpoint;
20
+ }
21
+ /** One declared Core Node. It proves its own complete degree, so both bounds hold. */
22
+ export interface CoreDegreeSubject {
23
+ readonly key: string;
24
+ readonly class: ClassRef;
25
+ readonly pointer: string;
26
+ }
27
+ export interface CoreEdgeSet<Edge> {
28
+ readonly edges: readonly CoreFactEdge<Edge>[];
29
+ /** The local exact Edge Classes, in the order their slices are checked. */
30
+ readonly classes: readonly {
31
+ readonly key: string;
32
+ readonly contract: EdgeContract;
33
+ }[];
34
+ readonly nodes: readonly CoreDegreeSubject[];
35
+ readonly satisfies: (ref: ClassRef, accepts: readonly ClassRef[]) => boolean;
36
+ /** The keys an Edge holds under the tuples covering its Class. */
37
+ readonly keys: (edge: CoreFactEdge<Edge>) => readonly string[];
38
+ /** Whether an undirected Edge's two endpoints state one contract, counted once. */
39
+ readonly sameIncident: (left: IncidentEndpoint, right: IncidentEndpoint) => boolean;
40
+ readonly report: (issue: CoreEdgeIssue<Edge>) => void;
41
+ }
42
+ export type CoreEdgeIssue<Edge> = {
43
+ readonly kind: 'identity';
44
+ readonly edge: Edge;
45
+ readonly previous: Edge;
46
+ } | {
47
+ readonly kind: 'no-self';
48
+ readonly edge: Edge;
49
+ } | {
50
+ readonly kind: 'key';
51
+ readonly edge: Edge;
52
+ readonly previous: Edge;
53
+ } | {
54
+ readonly kind: 'acyclic';
55
+ readonly class: string;
56
+ } | {
57
+ readonly kind: 'degree';
58
+ readonly pointer: string;
59
+ readonly endpoint: EdgeEndpointBase;
60
+ readonly count: EdgeCount;
61
+ readonly degree: number;
62
+ };
63
+ /** Whether the endpoints satisfy the contract: in order when directed, in either order when not. */
64
+ export declare function coreEndpointsSatisfy<Endpoint>(contract: EdgeContract, source: Endpoint, target: Endpoint, satisfies: (endpoint: Endpoint, accepts: readonly ClassRef[]) => boolean): boolean;
65
+ /**
66
+ * Report each fact that holds a key an earlier fact holds, with the first holder of that key. Core
67
+ * facts have no slug, so only tuples of endpoints and Properties bind them (DV-NODE-UNIQUE,
68
+ * DV-EDGE-UNIQUE); Activation checks them against stored data.
69
+ */
70
+ 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. */
72
+ export declare function checkCoreEdgeSet<Edge>(set: CoreEdgeSet<Edge>): void;
@@ -0,0 +1,95 @@
1
+ import { compareUnicode } from '../../addressing/ordering.js';
2
+ import { countBounds, hasDirectedCycle } from '../class/inheritance.js';
3
+ /** Whether the endpoints satisfy the contract: in order when directed, in either order when not. */
4
+ export function coreEndpointsSatisfy(contract, source, target, satisfies) {
5
+ if (contract.orientation === 'directed') {
6
+ const { endpoints } = contract;
7
+ return (satisfies(source, endpoints.source.accepts) && satisfies(target, endpoints.target.accepts));
8
+ }
9
+ const [first, second] = contract.endpoints;
10
+ return ((satisfies(source, first.accepts) && satisfies(target, second.accepts)) ||
11
+ (satisfies(source, second.accepts) && satisfies(target, first.accepts)));
12
+ }
13
+ /**
14
+ * Report each fact that holds a key an earlier fact holds, with the first holder of that key. Core
15
+ * facts have no slug, so only tuples of endpoints and Properties bind them (DV-NODE-UNIQUE,
16
+ * DV-EDGE-UNIQUE); Activation checks them against stored data.
17
+ */
18
+ export function checkHeldKeys(facts, keys, report) {
19
+ const holders = new Map();
20
+ for (const fact of facts) {
21
+ const held = keys(fact);
22
+ const holder = held.find((key) => holders.has(key));
23
+ if (holder !== undefined)
24
+ report(fact, holders.get(holder));
25
+ for (const key of held)
26
+ if (!holders.has(key))
27
+ holders.set(key, fact);
28
+ }
29
+ }
30
+ /** Check normalized Edge identity, noSelf, keys, exact-Class acyclicity and Core degrees. */
31
+ export function checkCoreEdgeSet(set) {
32
+ const identities = new Map();
33
+ const slices = new Map(set.classes.map(({ key }) => [key, []]));
34
+ for (const fact of set.edges) {
35
+ const [source, target] = fact.contract.orientation === 'undirected' &&
36
+ compareUnicode(fact.source.key, fact.target.key) > 0
37
+ ? [fact.target.key, fact.source.key]
38
+ : [fact.source.key, fact.target.key];
39
+ const identity = `${fact.class}\u0000${source}\u0000${target}`;
40
+ const previous = identities.get(identity);
41
+ if (previous !== undefined)
42
+ set.report({ kind: 'identity', edge: fact.edge, previous });
43
+ else
44
+ identities.set(identity, fact.edge);
45
+ slices.get(fact.class)?.push(fact);
46
+ if (fact.contract.constraints.noSelf === true && fact.source.key === fact.target.key)
47
+ set.report({ kind: 'no-self', edge: fact.edge });
48
+ }
49
+ checkHeldKeys(set.edges, set.keys, (fact, previous) => set.report({ kind: 'key', edge: fact.edge, previous: previous.edge }));
50
+ for (const { key, contract } of set.classes) {
51
+ const slice = slices.get(key);
52
+ if (contract.constraints.acyclic === true && hasDirectedCycle(slice))
53
+ set.report({ kind: 'acyclic', class: key });
54
+ checkDegrees(set, contract, slice);
55
+ }
56
+ }
57
+ function checkDegrees(set, contract, slice) {
58
+ for (const subject of degreeSubjects(set.nodes, slice)) {
59
+ const check = (endpoint, count, degree) => {
60
+ const [minimum, maximum] = countBounds(count);
61
+ if (degree > maximum || (degree < minimum && subject.minimum))
62
+ set.report({ kind: 'degree', pointer: subject.pointer, endpoint, count, degree });
63
+ };
64
+ if (contract.orientation === 'directed') {
65
+ const { source, target } = contract.endpoints;
66
+ if (set.satisfies(subject.class, source.accepts))
67
+ check(source, source.outgoing, slice.filter((edge) => edge.source.key === subject.key).length);
68
+ if (set.satisfies(subject.class, target.accepts))
69
+ check(target, target.incoming, slice.filter((edge) => edge.target.key === subject.key).length);
70
+ continue;
71
+ }
72
+ const [first, second] = contract.endpoints;
73
+ const degree = slice.filter((edge) => edge.source.key === subject.key || edge.target.key === subject.key).length;
74
+ if (set.satisfies(subject.class, first.accepts))
75
+ check(first, first.incident, degree);
76
+ if (!set.sameIncident(first, second) && set.satisfies(subject.class, second.accepts))
77
+ check(second, second.incident, degree);
78
+ }
79
+ }
80
+ /**
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.
84
+ */
85
+ function degreeSubjects(nodes, slice) {
86
+ const subjects = new Map(nodes.map((node) => [node.key, { ...node, minimum: true }]));
87
+ for (const edge of slice) {
88
+ for (const { key, member } of [edge.source, edge.target]) {
89
+ if (member === undefined || subjects.has(key))
90
+ continue;
91
+ subjects.set(key, { key, class: member.class, pointer: member.pointer, minimum: false });
92
+ }
93
+ }
94
+ return subjects.values();
95
+ }
@@ -18,3 +18,8 @@ export declare const name: Readonly<{
18
18
  }>;
19
19
  /** The common source and compiled grammar for a name displayed exactly as authored. */
20
20
  export declare function isDomainNameText(value: string): boolean;
21
+ /**
22
+ * The same grammar as a portable pattern: no control character, and neither first nor last a
23
+ * character `trim` removes, spelled out since `\s` differs between regular expression engines.
24
+ */
25
+ export declare const DOMAIN_NAME_TEXT_PATTERN: RegExp;
@@ -22,3 +22,10 @@ const DOMAIN_NAME_CONTROL = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/u;
22
22
  export function isDomainNameText(value) {
23
23
  return value.trim() === value && !DOMAIN_NAME_CONTROL.test(value);
24
24
  }
25
+ /**
26
+ * The same grammar as a portable pattern: no control character, and neither first nor last a
27
+ * character `trim` removes, spelled out since `\s` differs between regular expression engines.
28
+ */
29
+ export const DOMAIN_NAME_TEXT_PATTERN =
30
+ // oxlint-disable-next-line no-control-regex
31
+ /^(?:[^\u0000-\u001f\u007f-\u009f\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff](?:[^\u0000-\u001f\u007f-\u009f\u2028\u2029]*[^\u0000-\u001f\u007f-\u009f\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff])?)?$/u;
@@ -18,3 +18,11 @@ export declare function capBranches(values: readonly PatternBranch[]): readonly
18
18
  export declare function isConnected(branch: PatternBranch): boolean;
19
19
  export declare function targetMode(branch: PatternBranch): PolicyTargetMode | undefined;
20
20
  export declare function termKey(term: PolicyPatternTerm, namespace: string): string;
21
+ /** Disjoint sets of term keys, merged by the equalities of one Policy branch. */
22
+ export declare class UnionFind {
23
+ #private;
24
+ constructor(values: Iterable<string>);
25
+ add(value: string): void;
26
+ find(value: string): string;
27
+ join(left: string, right: string): void;
28
+ }
@@ -146,7 +146,8 @@ function collectVariableClasses(pattern, namespace) {
146
146
  function variableKey(namespace, id) {
147
147
  return `variable:${namespace}:${id}`;
148
148
  }
149
- class UnionFind {
149
+ /** Disjoint sets of term keys, merged by the equalities of one Policy branch. */
150
+ export class UnionFind {
150
151
  #parents = new Map();
151
152
  constructor(values) {
152
153
  for (const value of values)
@@ -1,4 +1,5 @@
1
- import type { PolicyCheckExpression } from '../model/policy.js';
1
+ import type { PolicyRef } from '../../../addressing/index.js';
2
+ import type { PolicyCheckExpression, PolicyExpression } from '../model/policy.js';
2
3
  export declare const MAX_PATTERN_DEPTH = 4;
3
4
  export declare const MAX_BRANCHES = 8;
4
5
  export declare const MAX_VARIABLES = 4;
@@ -8,6 +9,17 @@ export declare const MAX_DOMAINS = 4;
8
9
  export declare const MAX_CHECK_DEPTH = 4;
9
10
  export declare const MAX_CHECK_LEAVES = 8;
10
11
  export declare const BRANCH_OVERFLOW: number;
12
+ export interface PatternMeasure {
13
+ readonly depth: number;
14
+ readonly edgeCounts: readonly number[];
15
+ readonly domains: ReadonlySet<string>;
16
+ }
17
+ /**
18
+ * Measure named Policies by their expanded branches, depth and referenced Domains. A Policy has no
19
+ * measure when it is not normalized, when its expansion reaches itself, or when a composition names
20
+ * a Policy `isLocal` refuses or one without a measure.
21
+ */
22
+ export declare function policyMeasurer(origin: string, normalized: ReadonlyMap<string, PolicyExpression>, isLocal: (ref: PolicyRef) => boolean): (name: string) => PatternMeasure | undefined;
11
23
  export interface CheckMeasure {
12
24
  readonly depth: number;
13
25
  readonly branches: number;
@@ -1,3 +1,4 @@
1
+ import { policyObjectOrigin, policyVariableClass } from '../model/policy.js';
1
2
  export const MAX_PATTERN_DEPTH = 4;
2
3
  export const MAX_BRANCHES = 8;
3
4
  export const MAX_VARIABLES = 4;
@@ -7,6 +8,109 @@ export const MAX_DOMAINS = 4;
7
8
  export const MAX_CHECK_DEPTH = 4;
8
9
  export const MAX_CHECK_LEAVES = 8;
9
10
  export const BRANCH_OVERFLOW = MAX_BRANCHES + 1;
11
+ /**
12
+ * Measure named Policies by their expanded branches, depth and referenced Domains. A Policy has no
13
+ * measure when it is not normalized, when its expansion reaches itself, or when a composition names
14
+ * a Policy `isLocal` refuses or one without a measure.
15
+ */
16
+ export function policyMeasurer(origin, normalized, isLocal) {
17
+ // Measure every distinct composition node once. A node's expanded measure is independent of the
18
+ // path that reaches it (the only path-sensitive outcome, the back-edge `undefined`, already holds
19
+ // for every node on or reaching a composition cycle, regardless of entry), so caching the
20
+ // completed result by node identity turns path enumeration over the composition DAG into work
21
+ // linear in the number of nodes while preserving the exact measure.
22
+ const measures = new Map();
23
+ const measurePolicy = (name, visiting) => {
24
+ if (visiting.has(name))
25
+ return undefined;
26
+ if (measures.has(name))
27
+ return measures.get(name);
28
+ const declaration = normalized.get(name);
29
+ if (declaration === undefined)
30
+ return undefined;
31
+ const nested = new Set(visiting);
32
+ nested.add(name);
33
+ const measure = measureDeclaration(declaration, nested);
34
+ measures.set(name, measure);
35
+ return measure;
36
+ };
37
+ const measureDeclaration = (declaration, nested) => {
38
+ if ('match' in declaration)
39
+ return measurePattern(declaration.match, origin);
40
+ const refs = 'allOf' in declaration ? declaration.allOf : declaration.anyOf;
41
+ const children = refs.flatMap((ref) => {
42
+ if (!isLocal(ref))
43
+ return [];
44
+ const value = measurePolicy(ref.name, nested);
45
+ return value === undefined ? [] : [value];
46
+ });
47
+ if (children.length !== refs.length || children.length === 0)
48
+ return undefined;
49
+ return combineMeasures('allOf' in declaration ? 'allOf' : 'anyOf', children);
50
+ };
51
+ return (name) => measurePolicy(name, new Set());
52
+ }
53
+ function measurePattern(pattern, origin) {
54
+ if ('sameNode' in pattern)
55
+ return {
56
+ depth: 1,
57
+ edgeCounts: [1],
58
+ domains: new Set([
59
+ origin,
60
+ ...[pattern.sameNode.left, pattern.sameNode.right].flatMap((term) => term.kind === 'ref' ? [policyObjectOrigin(term.ref)] : []),
61
+ ]),
62
+ };
63
+ if ('source' in pattern)
64
+ return {
65
+ depth: 1,
66
+ edgeCounts: [1],
67
+ domains: new Set([
68
+ origin,
69
+ pattern.class.origin,
70
+ ...[pattern.source, pattern.target].flatMap((term) => term.kind === 'ref' ? [policyObjectOrigin(term.ref)] : []),
71
+ ]),
72
+ };
73
+ if ('exists' in pattern) {
74
+ const child = measurePattern(pattern.exists.where, origin);
75
+ return {
76
+ depth: child.depth + 1,
77
+ edgeCounts: child.edgeCounts,
78
+ domains: new Set([
79
+ ...child.domains,
80
+ ...pattern.exists.nodes.flatMap((node) => {
81
+ const ref = policyVariableClass(node);
82
+ return ref === undefined ? [] : [ref.origin];
83
+ }),
84
+ ]),
85
+ };
86
+ }
87
+ return combineMeasures('allOf' in pattern ? 'allOf' : 'anyOf', ('allOf' in pattern ? pattern.allOf : pattern.anyOf).map((child) => measurePattern(child, origin)));
88
+ }
89
+ function combineMeasures(kind, children) {
90
+ const depth = 1 + Math.max(0, ...children.map((child) => child.depth));
91
+ const domains = new Set(children.flatMap((child) => [...child.domains]));
92
+ if (kind === 'anyOf')
93
+ return {
94
+ depth,
95
+ edgeCounts: children.flatMap((child) => child.edgeCounts).slice(0, BRANCH_OVERFLOW),
96
+ domains,
97
+ };
98
+ let edgeCounts = [0];
99
+ for (const child of children) {
100
+ const next = [];
101
+ for (const left of edgeCounts) {
102
+ for (const right of child.edgeCounts) {
103
+ next.push(left + right);
104
+ if (next.length >= BRANCH_OVERFLOW)
105
+ break;
106
+ }
107
+ if (next.length >= BRANCH_OVERFLOW)
108
+ break;
109
+ }
110
+ edgeCounts = next;
111
+ }
112
+ return { depth, edgeCounts, domains };
113
+ }
10
114
  export function measureChecks(expression) {
11
115
  if ('check' in expression || 'sameNode' in expression)
12
116
  return { depth: 1, branches: 1, leaves: 1 };
@@ -0,0 +1,48 @@
1
+ import type { ClassRef, PolicyRef } from '../../../addressing/index.js';
2
+ import type { EdgeContract } from '../../class/model/edge.js';
3
+ import type { PolicyExpression, PolicyObjectRef } from '../model/policy.js';
4
+ import type { CheckLeafClasses } from './checks.js';
5
+ import { type PatternBranch } from './branches.js';
6
+ export type ObjectContract = {
7
+ readonly kind: 'default';
8
+ } | {
9
+ readonly kind: 'exact';
10
+ readonly ref: ClassRef;
11
+ };
12
+ export type PolicyUseContract = {
13
+ readonly kind: 'object';
14
+ readonly object: ObjectContract;
15
+ } | {
16
+ readonly kind: 'edge';
17
+ readonly source: ObjectContract;
18
+ readonly target: ObjectContract;
19
+ };
20
+ /** What Policy typing reads of the closure it types against: source or compiled admission. */
21
+ export interface PolicyTypingScope {
22
+ readonly normalized: ReadonlyMap<string, PolicyExpression>;
23
+ readonly allNodeClasses: readonly ClassRef[];
24
+ readonly concreteNodeClasses: readonly ClassRef[];
25
+ isLocalPolicy(ref: PolicyRef): boolean;
26
+ /** Whether `candidate` is the `required` Class or a same-kind descendant. */
27
+ classExtends(candidate: ClassRef, required: ClassRef): boolean;
28
+ edgeDeclaration(ref: ClassRef): EdgeContract | undefined;
29
+ /** The contract of a Node a Policy pattern references, `undefined` when it has none. */
30
+ referenceContract(ref: PolicyObjectRef): ObjectContract | undefined;
31
+ }
32
+ export declare class PolicyTyping {
33
+ #private;
34
+ readonly scope: PolicyTypingScope;
35
+ constructor(scope: PolicyTypingScope);
36
+ /** The normalized branches of a named Policy, `undefined` when it does not expand. */
37
+ expandedBranches(name: string, namespace: string): readonly PatternBranch[] | undefined;
38
+ policyAccepts(name: string, object: ObjectContract): boolean;
39
+ /** Whether the Policy accepts every concrete source and target pair the Edge Class accepts. */
40
+ policyAcceptsEdgeClass(name: string, declaration: EdgeContract): boolean;
41
+ /**
42
+ * How a check of the Policy types against each contract of its object, `undefined` when the
43
+ * Policy does not expand or one of its branches is typed for no contract.
44
+ */
45
+ checkLeafClasses(name: string, contracts: readonly ObjectContract[]): CheckLeafClasses | undefined;
46
+ endpointsOverlap(left: readonly ClassRef[], right: readonly ClassRef[]): boolean;
47
+ branchIsTyped(branch: PatternBranch, use: PolicyUseContract): boolean;
48
+ }