@astrale-os/kernel-dsl 0.2.0-beta.46 → 0.2.0-beta.48

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 (159) hide show
  1. package/dist/v1/addressing/patterns.d.ts +0 -5
  2. package/dist/v1/addressing/patterns.js +5 -5
  3. package/dist/v1/builder/callable/authoring.d.ts +1 -1
  4. package/dist/v1/builder/class/authoring.d.ts +0 -12
  5. package/dist/v1/builder/class/property.d.ts +4 -2
  6. package/dist/v1/builder/class/property.js +9 -1
  7. package/dist/v1/builder/index.d.ts +1 -1
  8. package/dist/v1/builder/lowering/callable.d.ts +2 -1
  9. package/dist/v1/builder/lowering/core.d.ts +3 -2
  10. package/dist/v1/builder/lowering/schema/compile.js +15 -2
  11. package/dist/v1/builder/lowering/value/compile.js +3 -0
  12. package/dist/v1/builder/lowering/value/zod.d.ts +3 -2
  13. package/dist/v1/builder/lowering/view.d.ts +4 -3
  14. package/dist/v1/builder/lowering/view.js +2 -14
  15. package/dist/v1/builder/schema/define.d.ts +4 -2
  16. package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
  17. package/dist/v1/builder/schema/handle.d.ts +2 -1
  18. package/dist/v1/builder/schema/input.d.ts +8 -0
  19. package/dist/v1/builder/schema/projection/callable.d.ts +51 -0
  20. package/dist/v1/builder/schema/projection/callable.js +1 -0
  21. package/dist/v1/builder/schema/projection/class.d.ts +89 -0
  22. package/dist/v1/builder/schema/projection/class.js +1 -0
  23. package/dist/v1/builder/schema/projection/core-input.d.ts +32 -0
  24. package/dist/v1/builder/schema/projection/core-input.js +1 -0
  25. package/dist/v1/builder/schema/projection/domain.d.ts +59 -0
  26. package/dist/v1/builder/schema/projection/domain.js +1 -0
  27. package/dist/v1/builder/schema/projection/identity.d.ts +39 -0
  28. package/dist/v1/builder/schema/projection/identity.js +1 -0
  29. package/dist/v1/builder/schema/projection/model.d.ts +59 -0
  30. package/dist/v1/builder/schema/projection/model.js +1 -0
  31. package/dist/v1/builder/schema/projection/property.d.ts +32 -0
  32. package/dist/v1/builder/schema/projection/property.js +1 -0
  33. package/dist/v1/builder/schema/projection/resolved-class.d.ts +76 -0
  34. package/dist/v1/builder/schema/projection/resolved-class.js +1 -0
  35. package/dist/v1/builder/schema/projection.d.ts +4 -375
  36. package/dist/v1/builder/view/authoring.d.ts +3 -7
  37. package/dist/v1/builder/view/authoring.js +5 -2
  38. package/dist/v1/bundle/structure.d.ts +2 -1
  39. package/dist/v1/compiled/admission/accept.d.ts +1 -1
  40. package/dist/v1/compiled/admission/accept.js +7 -1001
  41. package/dist/v1/compiled/admission/closure.d.ts +3 -0
  42. package/dist/v1/compiled/admission/closure.js +173 -0
  43. package/dist/v1/compiled/admission/diagnostic.d.ts +7 -0
  44. package/dist/v1/compiled/admission/diagnostic.js +13 -0
  45. package/dist/v1/compiled/admission/revision.js +2 -0
  46. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +5 -0
  47. package/dist/v1/compiled/admission/semantics/cardinality.js +113 -0
  48. package/dist/v1/compiled/admission/semantics/class.d.ts +5 -0
  49. package/dist/v1/compiled/admission/semantics/class.js +125 -0
  50. package/dist/v1/compiled/admission/semantics/context.d.ts +60 -0
  51. package/dist/v1/compiled/admission/semantics/context.js +81 -0
  52. package/dist/v1/compiled/admission/semantics/core.d.ts +35 -0
  53. package/dist/v1/compiled/admission/semantics/core.js +123 -0
  54. package/dist/v1/compiled/admission/semantics/edge.d.ts +8 -0
  55. package/dist/v1/compiled/admission/semantics/edge.js +89 -0
  56. package/dist/v1/compiled/admission/semantics/policy/admit.d.ts +4 -0
  57. package/dist/v1/compiled/admission/semantics/policy/admit.js +57 -0
  58. package/dist/v1/compiled/admission/semantics/policy/callable.d.ts +5 -0
  59. package/dist/v1/compiled/admission/semantics/policy/callable.js +72 -0
  60. package/dist/v1/compiled/admission/semantics/policy/contracts.d.ts +24 -0
  61. package/dist/v1/compiled/admission/semantics/policy/contracts.js +45 -0
  62. package/dist/v1/compiled/admission/semantics/policy/equality.d.ts +8 -0
  63. package/dist/v1/compiled/admission/semantics/policy/equality.js +27 -0
  64. package/dist/v1/compiled/admission/semantics/policy/measure.d.ts +9 -0
  65. package/dist/v1/compiled/admission/semantics/policy/measure.js +81 -0
  66. package/dist/v1/compiled/admission/semantics/policy/parity.d.ts +4 -0
  67. package/dist/v1/compiled/admission/semantics/policy/parity.js +126 -0
  68. package/dist/v1/compiled/admission/semantics/policy/pattern.d.ts +8 -0
  69. package/dist/v1/compiled/admission/semantics/policy/pattern.js +78 -0
  70. package/dist/v1/compiled/admission/semantics/policy/typing.d.ts +28 -0
  71. package/dist/v1/compiled/admission/semantics/policy/typing.js +206 -0
  72. package/dist/v1/compiled/admission/semantics/policy/variants.d.ts +10 -0
  73. package/dist/v1/compiled/admission/semantics/policy/variants.js +65 -0
  74. package/dist/v1/compiled/admission/semantics.d.ts +1 -1
  75. package/dist/v1/compiled/admission/semantics.js +8 -1214
  76. package/dist/v1/compiled/admission/wire/callable.d.ts +6 -0
  77. package/dist/v1/compiled/admission/wire/callable.js +90 -0
  78. package/dist/v1/compiled/admission/wire/class.d.ts +4 -0
  79. package/dist/v1/compiled/admission/wire/class.js +117 -0
  80. package/dist/v1/compiled/admission/wire/core.d.ts +5 -0
  81. package/dist/v1/compiled/admission/wire/core.js +32 -0
  82. package/dist/v1/compiled/admission/wire/dependency.d.ts +14 -0
  83. package/dist/v1/compiled/admission/wire/dependency.js +43 -0
  84. package/dist/v1/compiled/admission/wire/entry.d.ts +5 -0
  85. package/dist/v1/compiled/admission/wire/entry.js +100 -0
  86. package/dist/v1/compiled/admission/wire/pattern.d.ts +12 -0
  87. package/dist/v1/compiled/admission/wire/pattern.js +111 -0
  88. package/dist/v1/compiled/admission/wire/policy.d.ts +5 -0
  89. package/dist/v1/compiled/admission/wire/policy.js +168 -0
  90. package/dist/v1/compiled/admission/wire/program.d.ts +4 -0
  91. package/dist/v1/compiled/admission/wire/program.js +52 -0
  92. package/dist/v1/compiled/admission/wire/property.d.ts +4 -0
  93. package/dist/v1/compiled/admission/wire/property.js +74 -0
  94. package/dist/v1/compiled/admission/wire/reference.d.ts +8 -0
  95. package/dist/v1/compiled/admission/wire/reference.js +33 -0
  96. package/dist/v1/compiled/admission/wire/value.d.ts +29 -0
  97. package/dist/v1/compiled/admission/wire/value.js +98 -0
  98. package/dist/v1/compiled/admission/wire/view.d.ts +3 -0
  99. package/dist/v1/compiled/admission/wire/view.js +17 -0
  100. package/dist/v1/compiled/compiler/compile.js +3 -0
  101. package/dist/v1/compiled/model/schema.d.ts +5 -1
  102. package/dist/v1/domain/create.js +0 -19
  103. package/dist/v1/domain/model/domain.d.ts +0 -1
  104. package/dist/v1/domain/projection/classes.js +2 -9
  105. package/dist/v1/index.d.ts +1 -1
  106. package/dist/v1/language/class/inheritance.d.ts +12 -0
  107. package/dist/v1/language/class/inheritance.js +25 -0
  108. package/dist/v1/language/class/model/property.d.ts +3 -0
  109. package/dist/v1/language/class/resolved.d.ts +5 -1
  110. package/dist/v1/language/definition/metadata.d.ts +8 -0
  111. package/dist/v1/language/definition/metadata.js +12 -0
  112. package/dist/v1/language/value/model/blob.d.ts +0 -2
  113. package/dist/v1/language/value/model/blob.js +2 -2
  114. package/dist/v1/language/value/model/instant.d.ts +0 -2
  115. package/dist/v1/language/value/model/instant.js +1 -1
  116. package/dist/v1/language/value/model/schema.d.ts +2 -1
  117. package/dist/v1/language/value/model/schema.js +1 -5
  118. package/dist/v1/language/value/reference/registry.d.ts +2 -1
  119. package/dist/v1/language/value/regexp/syntax.d.ts +2 -1
  120. package/dist/v1/language/view/model/view.d.ts +2 -4
  121. package/dist/v1/language/view/resolved.d.ts +2 -2
  122. package/dist/v1/schema/analysis/class.d.ts +4 -2
  123. package/dist/v1/schema/analysis/class.js +1 -0
  124. package/dist/v1/schema/canonical/revision.d.ts +2 -1
  125. package/dist/v1/schema/compatibility/comparators/classes.d.ts +2 -1
  126. package/dist/v1/schema/compatibility/context.d.ts +6 -2
  127. package/dist/v1/schema/compatibility/context.js +12 -6
  128. package/dist/v1/schema/compatibility/coordinate.d.ts +2 -1
  129. package/dist/v1/schema/compatibility/fields.d.ts +2 -0
  130. package/dist/v1/schema/compatibility/fields.js +1 -0
  131. package/dist/v1/schema/compatibility/references.js +1 -10
  132. package/dist/v1/schema/compatibility/report.d.ts +2 -1
  133. package/dist/v1/schema/compatibility/scope.d.ts +0 -4
  134. package/dist/v1/schema/compatibility/scope.js +1 -1
  135. package/dist/v1/schema/diagnostic.d.ts +1 -1
  136. package/dist/v1/schema/model/schema.d.ts +5 -1
  137. package/dist/v1/schema/normalization/domain.js +12 -6
  138. package/dist/v1/schema/resolution/definition.index.d.ts +1 -2
  139. package/dist/v1/schema/resolution/definition.index.js +0 -3
  140. package/dist/v1/schema/resolution/domain.d.ts +4 -1
  141. package/dist/v1/schema/resolution/domain.js +6 -7
  142. package/dist/v1/schema/resolution/origins.d.ts +1 -1
  143. package/dist/v1/schema/resolution/origins.js +0 -5
  144. package/dist/v1/schema/validation/class.js +19 -50
  145. package/dist/v1/schema/validation/core.js +1 -27
  146. package/dist/v1/schema/validation/domain.d.ts +2 -1
  147. package/dist/v1/schema/validation/domain.structure.d.ts +0 -2
  148. package/dist/v1/schema/validation/domain.structure.js +9 -8
  149. package/dist/v1/schema/validation/policy/context.js +5 -16
  150. package/dist/v1/schema/validation/view.js +14 -9
  151. package/dist/value/accept.d.ts +1 -1
  152. package/dist/value/accept.js +32 -6
  153. package/dist/value/canonical.js +10 -2
  154. package/dist/value/immutable.optimization.d.ts +4 -1
  155. package/dist/value/immutable.optimization.js +8 -2
  156. package/dist/value/limits.d.ts +1 -1
  157. package/dist/value/unicode.d.ts +3 -0
  158. package/dist/value/unicode.js +27 -0
  159. package/package.json +1 -1
@@ -1,17 +1,9 @@
1
- import { json } from '@astrale-os/kernel-dsl/value';
2
- import { Key, PropertyKey as PropertyKeyValue } from '../../addressing/index.js';
3
- import { compareUnicode } from '../../addressing/ordering.js';
4
- import { composeMethods } from '../../language/callable/composition.js';
5
- import { isMethodExecutable } from '../../language/callable/executable.js';
6
- import { policyObjectKey, policyObjectOrigin, policyVariableClass, projectedMemberClass, } from '../../language/index.js';
7
- import { branchesOf, branchProduct, capBranches, isConnected, targetMode, termKey, } from '../../language/policy/analysis/branches.js';
8
- import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, } from '../../language/policy/analysis/budgets.js';
9
- import { admittedCheckClasses, neverAdmittedClasses, } from '../../language/policy/analysis/checks.js';
10
- import { normalizePolicyExpression } from '../../language/policy/normalization/normalize.js';
11
- import { bindValueProgram } from '../../language/value/compile/load.js';
12
- import { validateValueProgram } from '../../language/value/evaluate/program.js';
13
- import { GRAPH_NODE_PATH_SCHEMA_ID } from '../../language/value/syntax/node-path/schema.js';
14
- import { invalidCompiled } from '../diagnostic.js';
1
+ import {} from '../model/schema.js';
2
+ import { analyzeClasses, validateClassFacets } from './semantics/class.js';
3
+ import { index, admittedOrigins } from './semantics/context.js';
4
+ import { validateCore } from './semantics/core.js';
5
+ import { validateEdges } from './semantics/edge.js';
6
+ import { validatePolicies } from './semantics/policy/admit.js';
15
7
  /**
16
8
  * Re-admit contextual Language laws after the portable compiled grammar and
17
9
  * exact closure have been established. This module deliberately depends only
@@ -21,1207 +13,9 @@ export function validateCompiledSemantics(candidate) {
21
13
  const partial = index(candidate);
22
14
  const analyses = analyzeClasses(partial.classes);
23
15
  const context = { ...partial, analyses };
24
- const admitted = admittedOrigins(candidate);
25
16
  validateClassFacets(context);
26
- validateEdges(context, admitted);
27
- validateViews(candidate, context);
17
+ const admitted = admittedOrigins(candidate);
18
+ validateEdges(context);
28
19
  validatePolicies(candidate, context, admitted);
29
20
  validateCore(candidate, context);
30
21
  }
31
- function index(candidate) {
32
- const definitions = new Map();
33
- const classes = new Map();
34
- const policies = new Map();
35
- const core = new Map();
36
- for (const [entryIndex, entry] of candidate.closure.entries()) {
37
- const collections = [
38
- ['classes', entry.definitions.classes],
39
- ['functions', entry.definitions.functions],
40
- ['policies', entry.definitions.policies],
41
- ['views', entry.definitions.views],
42
- ['core', entry.definitions.core],
43
- ];
44
- for (const [collection, values] of collections) {
45
- for (const [valueIndex, value] of values.entries()) {
46
- const located = {
47
- value,
48
- entry,
49
- entryIndex,
50
- index: valueIndex,
51
- pointer: `/closure/${entryIndex}/definitions/${collection}/${valueIndex}`,
52
- };
53
- definitions.set(Key.of(value.ref), {
54
- ...located,
55
- value: value.ref,
56
- });
57
- if (value.ref.kind === 'class')
58
- classes.set(Key.of(value.ref), located);
59
- if (value.ref.kind === 'policy')
60
- policies.set(Key.of(value.ref), located);
61
- if (value.ref.kind === 'core')
62
- core.set(Key.of(value.ref), located);
63
- }
64
- }
65
- }
66
- return { definitions, classes, policies, core };
67
- }
68
- function analyzeClasses(classes) {
69
- const analyses = new Map();
70
- const visiting = new Set();
71
- const analyze = (located) => {
72
- const classKey = Key.of(located.value.ref);
73
- const cached = analyses.get(classKey);
74
- if (cached !== undefined)
75
- return cached;
76
- if (visiting.has(classKey))
77
- fail(`${located.pointer}/extends`, 'Class inheritance is cyclic.');
78
- visiting.add(classKey);
79
- const parents = located.value.extends.map((ref, index) => {
80
- const parent = classes.get(Key.of(ref));
81
- if (parent === undefined)
82
- fail(`${located.pointer}/extends/${index}`, 'Parent Class is absent.');
83
- return analyze(parent);
84
- });
85
- const properties = new Map();
86
- for (const parent of parents)
87
- for (const [key, value] of parent.properties)
88
- properties.set(key, value);
89
- for (const property of located.value.properties) {
90
- const key = PropertyKeyValue.of(located.value.ref, property.name);
91
- properties.set(key, {
92
- key,
93
- owner: located.value.ref,
94
- declaration: property,
95
- entry: located.entry,
96
- });
97
- }
98
- let dataOwner;
99
- for (const parent of parents) {
100
- if (parent.dataOwner === undefined)
101
- continue;
102
- if (dataOwner !== undefined && Key.of(dataOwner) !== Key.of(parent.dataOwner)) {
103
- fail(located.pointer, 'Data has distinct inherited declaring owners.');
104
- }
105
- dataOwner = parent.dataOwner;
106
- }
107
- if (located.value.kind === 'node' && located.value.data !== undefined) {
108
- if (dataOwner !== undefined)
109
- fail(`${located.pointer}/data`, 'Data cannot redeclare an inherited contract.');
110
- dataOwner = located.value.ref;
111
- }
112
- const own = new Map((located.value.kind === 'node' ? located.value.methods : []).map((method) => [
113
- method.name,
114
- {
115
- key: `${classKey}.method.${method.name}`,
116
- name: method.name,
117
- owner: located.value.ref,
118
- declaration: method,
119
- entry: located.entry,
120
- },
121
- ]));
122
- const composition = composeMethods(parents, own, located.value.abstract, (left, right) => sameMethodSignature(left.entry, left.declaration, right.entry, right.declaration), (message) => fail(`${located.pointer}/methods`, message));
123
- const analysis = {
124
- properties,
125
- ...composition,
126
- ...(dataOwner === undefined ? {} : { dataOwner }),
127
- };
128
- analyses.set(classKey, analysis);
129
- visiting.delete(classKey);
130
- return analysis;
131
- };
132
- for (const value of classes.values())
133
- analyze(value);
134
- return analyses;
135
- }
136
- function sameMethodSignature(leftEntry, left, rightEntry, right) {
137
- return (programKey(leftEntry, left.input) === programKey(rightEntry, right.input) &&
138
- outputKey(leftEntry, left.output) === outputKey(rightEntry, right.output) &&
139
- left.auth === right.auth &&
140
- left.static === right.static &&
141
- left.execution?.durable === right.execution?.durable);
142
- }
143
- function outputKey(entry, output) {
144
- return output.mode === 'binary' ? 'binary' : `${output.mode}:${programKey(entry, output.value)}`;
145
- }
146
- function programKey(entry, value) {
147
- return json.serialize(bindValueProgram(entry.values[value], entry.patterns));
148
- }
149
- function validateClassFacets(context) {
150
- for (const located of context.classes.values()) {
151
- if (located.value.abstract &&
152
- (located.value.policies.read !== undefined || located.value.policies.traverse !== undefined)) {
153
- fail(`${located.pointer}/policies`, 'Abstract Classes cannot declare observation Policies.');
154
- }
155
- for (const [index, property] of located.value.properties.entries()) {
156
- const path = `${located.pointer}/properties/${index}`;
157
- if (property.auto !== undefined) {
158
- if (property.required && !property.auto.on.includes('create')) {
159
- fail(`${path}/auto/on`, 'A required auto Property must run on create.');
160
- }
161
- if (property.auto.set === 'instant' && property.ordering !== 'instant') {
162
- fail(`${path}/auto/set`, 'The instant setter requires an Instant value program.');
163
- }
164
- }
165
- }
166
- if (located.value.kind === 'node') {
167
- for (const [index, method] of located.value.methods.entries()) {
168
- if (!isMethodExecutable(located.value, method) && method.policy !== undefined) {
169
- fail(`${located.pointer}/methods/${index}/policy`, 'An abstract Method cannot have a Policy.');
170
- }
171
- }
172
- }
173
- }
174
- }
175
- /**
176
- * The origins each closure entry was admitted against: itself and its transitive dependencies.
177
- * Source admission decides undirected overlap and types Policies within that closure alone, so a
178
- * dependent's Class extending a dependency's Classes never reaches the dependency's own decisions.
179
- */
180
- function admittedOrigins(candidate) {
181
- const entries = new Map(candidate.closure.map((entry) => [entry.origin, entry]));
182
- const scopes = new Map();
183
- const scope = (origin) => {
184
- const known = scopes.get(origin);
185
- if (known !== undefined)
186
- return known;
187
- const origins = new Set([origin]);
188
- for (const dependency of Object.values(entries.get(origin)?.dependencies ?? {}))
189
- for (const reached of scope(dependency.origin))
190
- origins.add(reached);
191
- scopes.set(origin, origins);
192
- return origins;
193
- };
194
- for (const entry of candidate.closure)
195
- scope(entry.origin);
196
- return scopes;
197
- }
198
- function validateEdges(context, admitted) {
199
- for (const located of context.classes.values()) {
200
- if (!isCompiledEdge(located.value))
201
- continue;
202
- const edge = located.value;
203
- const endpoints = edge.orientation === 'directed'
204
- ? [edge.endpoints.source, edge.endpoints.target]
205
- : edge.endpoints;
206
- if (endpoints[0].role === endpoints[1].role)
207
- fail(`${located.pointer}/endpoints`, 'Edge endpoint roles must differ.');
208
- for (const [endpointIndex, endpoint] of endpoints.entries()) {
209
- const seen = new Set();
210
- for (const [acceptIndex, ref] of endpoint.accepts.entries()) {
211
- const key = Key.of(ref);
212
- if (seen.has(key))
213
- fail(`${located.pointer}/endpoints/${endpointIndex}/accepts/${acceptIndex}`, 'Edge endpoint repeats an accepted Class.');
214
- seen.add(key);
215
- if (context.classes.get(key)?.value.kind !== 'node')
216
- fail(`${located.pointer}/endpoints/${endpointIndex}/accepts/${acceptIndex}`, 'Edge endpoint must accept a Node Class.');
217
- }
218
- }
219
- if (edge.orientation === 'undirected' &&
220
- !sameIncidentEndpoint(edge.endpoints[0], edge.endpoints[1]) &&
221
- undirectedOverlap(context, edge.endpoints, admitted.get(located.entry.origin))) {
222
- fail(`${located.pointer}/endpoints`, 'Unequal undirected endpoint contracts overlap.');
223
- }
224
- for (const [parentIndex, parentRef] of edge.extends.entries()) {
225
- const parent = context.classes.get(Key.of(parentRef))?.value;
226
- if (parent !== undefined && isCompiledEdge(parent) && !edgeRefines(context, edge, parent)) {
227
- fail(`${located.pointer}/extends/${parentIndex}`, 'Edge Class does not safely refine its parent.');
228
- }
229
- }
230
- }
231
- }
232
- function edgeRefines(context, child, parent) {
233
- if (child.orientation !== parent.orientation)
234
- return false;
235
- if (parent.constraints.noSelf === true && child.constraints.noSelf !== true)
236
- return false;
237
- if (parent.constraints.acyclic === true && child.constraints.acyclic !== true)
238
- return false;
239
- if (child.orientation === 'directed' && parent.orientation === 'directed') {
240
- return (endpointRefines(context, child.endpoints.source, parent.endpoints.source) &&
241
- countRefines(child.endpoints.source.outgoing, parent.endpoints.source.outgoing) &&
242
- endpointRefines(context, child.endpoints.target, parent.endpoints.target) &&
243
- countRefines(child.endpoints.target.incoming, parent.endpoints.target.incoming));
244
- }
245
- if (child.orientation !== 'undirected' || parent.orientation !== 'undirected')
246
- return false;
247
- const refine = (a, b) => endpointRefines(context, child.endpoints[a], parent.endpoints[b]) &&
248
- countRefines(child.endpoints[a].incident, parent.endpoints[b].incident);
249
- const direct = refine(0, 0) && refine(1, 1);
250
- if (direct)
251
- return true;
252
- return refine(0, 1) && refine(1, 0);
253
- }
254
- function endpointRefines(context, child, parent) {
255
- return child.accepts.every((accepted) => classSatisfiesAny(context, accepted, parent.accepts));
256
- }
257
- function countRefines(child, parent) {
258
- const [childMin, childMax] = countBounds(child);
259
- const [parentMin, parentMax] = countBounds(parent);
260
- return childMin >= parentMin && childMax <= parentMax;
261
- }
262
- function sameIncidentEndpoint(left, right) {
263
- const a = left.accepts.map(Key.of).sort(compareUnicode);
264
- const b = right.accepts.map(Key.of).sort(compareUnicode);
265
- return (left.incident === right.incident &&
266
- a.length === b.length &&
267
- a.every((value, index) => value === b[index]));
268
- }
269
- function undirectedOverlap(context, endpoints, origins) {
270
- return [...context.classes.values()].some(({ value }) => origins.has(value.ref.origin) &&
271
- value.kind === 'node' &&
272
- !value.abstract &&
273
- classSatisfiesAny(context, value.ref, endpoints[0].accepts) &&
274
- classSatisfiesAny(context, value.ref, endpoints[1].accepts));
275
- }
276
- function classSatisfiesAny(context, candidate, accepted) {
277
- const targets = new Set(accepted.map(Key.of));
278
- const visit = (ref, seen) => {
279
- const key = Key.of(ref);
280
- if (targets.has(key))
281
- return true;
282
- if (seen.has(key))
283
- return false;
284
- seen.add(key);
285
- return context.classes.get(key)?.value.extends.some((parent) => visit(parent, seen)) ?? false;
286
- };
287
- return visit(candidate, new Set());
288
- }
289
- function validateViews(candidate, context) {
290
- for (const [entryIndex, entry] of candidate.closure.entries()) {
291
- for (const [viewIndex, view] of entry.definitions.views.entries()) {
292
- if (view.target.kind !== 'definition')
293
- continue;
294
- for (const [targetIndex, ref] of view.target.definitions.entries()) {
295
- if (context.classes.get(Key.of(ref))?.value.kind !== 'node') {
296
- fail(`/closure/${entryIndex}/definitions/views/${viewIndex}/target/definitions/${targetIndex}`, 'A View Definition target must resolve to a Node Class.');
297
- }
298
- }
299
- }
300
- }
301
- }
302
- function validatePolicies(candidate, context, admitted) {
303
- for (const [entryIndex, entry] of candidate.closure.entries()) {
304
- const localPolicies = new Map(entry.definitions.policies.map((value) => [value.ref.name, value]));
305
- for (const [policyIndex, policy] of entry.definitions.policies.entries()) {
306
- validatePolicyExpression(context, entry, policy.expression, `/closure/${entryIndex}/definitions/policies/${policyIndex}/expression`, localPolicies);
307
- }
308
- validatePolicyCycles(entry, localPolicies, entryIndex);
309
- validatePolicyParity(context, entry, entryIndex, localPolicies, admitted.get(entry.origin));
310
- for (const [functionIndex, callable] of entry.definitions.functions.entries()) {
311
- if (callable.policy !== undefined)
312
- validateCallablePolicy(context, entry, callable.policy, typeof callable.input === 'number' ? callable.input : callable.input.metadata, callable.fields, false, `/closure/${entryIndex}/definitions/functions/${functionIndex}/policy`);
313
- }
314
- for (const [classIndex, definition] of entry.definitions.classes.entries()) {
315
- for (const [kind, ref] of Object.entries(definition.policies)) {
316
- if (ref !== undefined &&
317
- (ref.origin !== entry.origin || context.policies.get(Key.of(ref)) === undefined)) {
318
- fail(`/closure/${entryIndex}/definitions/classes/${classIndex}/policies/${kind}`, 'Class Policies must name a local Policy Definition.');
319
- }
320
- }
321
- if (definition.kind !== 'node')
322
- continue;
323
- for (const [methodIndex, callable] of definition.methods.entries()) {
324
- if (callable.policy !== undefined)
325
- validateCallablePolicy(context, entry, callable.policy, callable.input, callable.fields, !callable.static, `/closure/${entryIndex}/definitions/classes/${classIndex}/methods/${methodIndex}/policy`);
326
- }
327
- }
328
- }
329
- }
330
- function validatePolicyExpression(context, entry, expression, path, local) {
331
- if ('match' in expression) {
332
- validatePattern(context, expression.match, `${path}/match`, new Set(), new Set());
333
- return;
334
- }
335
- const values = 'allOf' in expression ? expression.allOf : expression.anyOf;
336
- if (values.length === 0)
337
- fail(path, 'Named Policy composition must be non-empty.');
338
- for (const [index, ref] of values.entries()) {
339
- if (ref.origin !== entry.origin || ref.kind !== 'policy' || !local.has(ref.name))
340
- fail(`${path}/${index}`, 'Named Policy composition must reference a local Policy.');
341
- }
342
- }
343
- function validatePattern(context, expression, path, scope, declared) {
344
- if ('sameNode' in expression) {
345
- validatePolicyTerm(context, expression.sameNode.left, `${path}/sameNode/left`, scope);
346
- validatePolicyTerm(context, expression.sameNode.right, `${path}/sameNode/right`, scope);
347
- return;
348
- }
349
- if ('source' in expression) {
350
- if (context.classes.get(Key.of(expression.class))?.value.kind !== 'edge')
351
- fail(`${path}/class`, 'Policy pattern Class must resolve to an Edge Class.');
352
- validatePolicyTerm(context, expression.source, `${path}/source`, scope);
353
- validatePolicyTerm(context, expression.target, `${path}/target`, scope);
354
- if (expression.repeat !== undefined &&
355
- (!Number.isInteger(expression.repeat.min) ||
356
- !Number.isInteger(expression.repeat.max) ||
357
- expression.repeat.min < 0 ||
358
- expression.repeat.max < expression.repeat.min ||
359
- expression.repeat.max > MAX_REPEAT))
360
- fail(`${path}/repeat`, 'Policy repeat is outside the V1 bounds.');
361
- return;
362
- }
363
- if ('exists' in expression) {
364
- if (expression.exists.nodes.length > MAX_VARIABLES) {
365
- fail(`${path}/exists/nodes`, 'Policy exists exceeds the V1 variable budget.');
366
- }
367
- const nested = new Set(scope);
368
- for (const [index, node] of expression.exists.nodes.entries()) {
369
- const id = node.variable.id;
370
- if (declared.has(id) || nested.has(id))
371
- fail(`${path}/exists/nodes/${index}/variable/id`, 'Policy variable IDs must be globally unique and cannot shadow.');
372
- declared.add(id);
373
- nested.add(id);
374
- const classRef = policyVariableClass(node);
375
- if (classRef !== undefined && context.classes.get(Key.of(classRef))?.value.kind !== 'node')
376
- fail(`${path}/exists/nodes/${index}/${'class' in node ? 'class' : 'selector/class'}`, 'Policy variable Class must resolve to a Node Class.');
377
- }
378
- validatePattern(context, expression.exists.where, `${path}/exists/where`, nested, declared);
379
- return;
380
- }
381
- const values = 'allOf' in expression ? expression.allOf : expression.anyOf;
382
- if (values.length === 0)
383
- fail(path, 'Policy pattern composition must be non-empty.');
384
- values.forEach((value, index) => validatePattern(context, value, `${path}/${index}`, scope, declared));
385
- }
386
- function validatePolicyTerm(context, term, path, scope) {
387
- if (term.kind === 'ref' && term.ref.kind === 'method')
388
- validatePolicyMethod(context, term.ref, `${path}/ref`);
389
- if (term.kind === 'variable' && !scope.has(term.id))
390
- fail(path, 'Policy variable is outside its declaring scope.');
391
- }
392
- function validatePolicyMethod(context, ref, path) {
393
- const owner = context.classes.get(Key.of(ref.owner))?.value;
394
- const method = owner?.kind === 'node' ? owner.methods.find((method) => method.name === ref.name) : undefined;
395
- if (method === undefined || !isMethodExecutable(owner, method))
396
- fail(path, 'Policy Method ref must name an exact concrete Method declaration.');
397
- }
398
- function validatePolicyCycles(entry, local, entryIndex) {
399
- const visiting = new Set();
400
- const visited = new Set();
401
- const visit = (name) => {
402
- if (visiting.has(name))
403
- fail(`/closure/${entryIndex}/definitions/policies`, `Named Policy composition cycle reaches ${name}.`);
404
- if (visited.has(name))
405
- return;
406
- visiting.add(name);
407
- const expression = local.get(name)?.expression;
408
- if (expression !== undefined && !('match' in expression)) {
409
- for (const ref of 'allOf' in expression ? expression.allOf : expression.anyOf)
410
- if (ref.origin === entry.origin && ref.kind === 'policy')
411
- visit(ref.name);
412
- }
413
- visiting.delete(name);
414
- visited.add(name);
415
- };
416
- for (const name of local.keys())
417
- visit(name);
418
- }
419
- function validateCallablePolicy(context, entry, expression, inputIndex, fields, instanceMethod, path) {
420
- if ('check' in expression || 'sameNode' in expression) {
421
- if ('check' in expression &&
422
- (expression.check.origin !== entry.origin ||
423
- context.policies.get(Key.of(expression.check)) === undefined))
424
- fail(`${path}/check`, 'Callable Policy must reference a local named Policy.');
425
- const objects = 'check' in expression ? { object: expression.object } : expression.sameNode;
426
- for (const [side, object] of Object.entries(objects)) {
427
- if (object.kind === 'ref' && object.ref.kind === 'method')
428
- validatePolicyMethod(context, object.ref, `${path}/${side}/ref`);
429
- if (object.kind === 'self' && !instanceMethod)
430
- fail(`${path}/${side}`, 'self is valid only for an instance Method.');
431
- if (object.kind === 'input' &&
432
- (fields === undefined ||
433
- fields[object.field] === undefined ||
434
- !isSingleNodePathField(context, entry, inputIndex, object.field))) {
435
- fail(`${path}/${side}`, 'Callable Policy input target must be a direct required single-Node Path field.');
436
- }
437
- }
438
- return;
439
- }
440
- const children = 'allOf' in expression ? expression.allOf : expression.anyOf;
441
- if (children.length === 0)
442
- fail(path, 'Callable Policy composition must be non-empty.');
443
- children.forEach((child, index) => validateCallablePolicy(context, entry, child, inputIndex, fields, instanceMethod, `${path}/${index}`));
444
- }
445
- function isSingleNodePathField(context, entry, inputIndex, field) {
446
- const rootValue = entry.values[inputIndex]?.nodes[entry.values[inputIndex].root]?.schema;
447
- if (rootValue === null || typeof rootValue !== 'object' || Array.isArray(rootValue))
448
- return false;
449
- const root = rootValue;
450
- const properties = root.properties;
451
- if (properties === null || typeof properties !== 'object' || Array.isArray(properties))
452
- return false;
453
- const schemaValue = properties[field];
454
- if (schemaValue === null || typeof schemaValue !== 'object' || Array.isArray(schemaValue))
455
- return false;
456
- const schema = schemaValue;
457
- const annotationValue = schema['x-astrale-path'];
458
- if (annotationValue === null ||
459
- typeof annotationValue !== 'object' ||
460
- Array.isArray(annotationValue))
461
- return false;
462
- const annotation = annotationValue;
463
- return (root.type === 'object' &&
464
- root.additionalProperties === false &&
465
- Array.isArray(root.required) &&
466
- root.required.includes(field) &&
467
- schema.$ref === GRAPH_NODE_PATH_SCHEMA_ID &&
468
- annotation.target === 'node' &&
469
- annotation.cardinality === 'one' &&
470
- Array.isArray(annotation.accepts) &&
471
- annotation.accepts.length > 0 &&
472
- annotation.accepts.every((ref) => isNodeClassRef(context, ref)));
473
- }
474
- function isNodeClassRef(context, value) {
475
- if (value === null || typeof value !== 'object' || Array.isArray(value))
476
- return false;
477
- const ref = value;
478
- if (typeof ref.origin !== 'string' || ref.kind !== 'class' || typeof ref.name !== 'string') {
479
- return false;
480
- }
481
- return context.classes.get(Key.of(ref))?.value.kind === 'node';
482
- }
483
- function validatePolicyParity(context, entry, entryIndex, policies, origins) {
484
- const normalized = new Map([...policies].map(([name, policy]) => [name, normalizePolicyExpression(policy.expression)]));
485
- const typing = new CompiledPolicyTyping(context, entry, normalized, origins);
486
- for (const [name, declaration] of policies) {
487
- const path = `/closure/${entryIndex}/definitions/policies/${declarationIndex(entry, 'policies', name)}`;
488
- if ('match' in declaration.expression) {
489
- for (const branch of branchesOf(declaration.expression.match, name)) {
490
- if (targetMode(branch) === undefined) {
491
- fail(`${path}/expression/match`, 'A Policy branch must target object or at least one of source and target.');
492
- }
493
- if (!isConnected(branch)) {
494
- fail(`${path}/expression/match`, 'A Policy branch must connect subject, every referenced protected term, and every scoped variable.');
495
- }
496
- }
497
- }
498
- const measure = measureCompiledPolicy(entry.origin, name, normalized, new Set());
499
- if (measure !== undefined &&
500
- (measure.depth > MAX_PATTERN_DEPTH ||
501
- measure.edgeCounts.length > MAX_BRANCHES ||
502
- Math.max(0, ...measure.edgeCounts) > MAX_EDGES ||
503
- measure.domains.size > MAX_DOMAINS)) {
504
- fail(path, 'Policy expansion exceeds the V1 depth, branch, Edge, or Domain budget.');
505
- }
506
- const branches = typing.expandedBranches(name, new Set(), `$${name}`);
507
- if (branches !== undefined) {
508
- for (const branch of branches) {
509
- const mode = targetMode(branch);
510
- if (mode === undefined)
511
- fail(path, 'Expanded Policy branches must use one coherent target mode.');
512
- const use = mode === 'object'
513
- ? { kind: 'object', object: { kind: 'default' } }
514
- : { kind: 'edge', source: { kind: 'default' }, target: { kind: 'default' } };
515
- if (!typing.branchIsTyped(branch, use))
516
- fail(path, 'A Policy branch has contradictory endpoint or repeat contracts.');
517
- }
518
- }
519
- }
520
- for (const [classIndex, definition] of entry.definitions.classes.entries()) {
521
- for (const operation of ['read', 'traverse']) {
522
- const policy = definition.policies[operation];
523
- if (policy === undefined)
524
- continue;
525
- const path = `/closure/${entryIndex}/definitions/classes/${classIndex}/policies/${operation}`;
526
- const label = operation === 'read' ? 'Read' : 'Traverse';
527
- if (definition.kind === 'node') {
528
- if (!typing.policyAccepts(policy.name, { kind: 'exact', ref: definition.ref })) {
529
- fail(path, `${label} Policy is incompatible with its protected Node Class.`);
530
- }
531
- }
532
- else if (isCompiledEdge(definition)) {
533
- const endpoints = definition.orientation === 'directed'
534
- ? [definition.endpoints.source, definition.endpoints.target]
535
- : definition.endpoints;
536
- const sources = typing.concreteEndpointClasses(endpoints[0].accepts);
537
- const targets = typing.concreteEndpointClasses(endpoints[1].accepts);
538
- if (sources.some((source) => targets.some((target) => !typing.policyAcceptsEdge(policy.name, { kind: 'exact', ref: source }, { kind: 'exact', ref: target })))) {
539
- fail(path, `${label} Policy is incompatible with an accepted Edge endpoint pair.`);
540
- }
541
- }
542
- }
543
- if (definition.kind === 'node') {
544
- for (const [methodIndex, method] of definition.methods.entries()) {
545
- if (method.policy !== undefined) {
546
- validateCallablePolicyTyping(context, typing, entry, method.policy, method.input, method.static ? undefined : definition.ref, `/closure/${entryIndex}/definitions/classes/${classIndex}/methods/${methodIndex}/policy`);
547
- }
548
- }
549
- }
550
- }
551
- for (const [functionIndex, callable] of entry.definitions.functions.entries()) {
552
- if (callable.policy !== undefined) {
553
- validateCallablePolicyTyping(context, typing, entry, callable.policy, typeof callable.input === 'number' ? callable.input : callable.input.metadata, undefined, `/closure/${entryIndex}/definitions/functions/${functionIndex}/policy`);
554
- }
555
- }
556
- }
557
- function declarationIndex(entry, kind, name) {
558
- return entry.definitions[kind].findIndex(({ ref }) => ref.name === name);
559
- }
560
- function validateCallablePolicyTyping(context, typing, entry, expression, input, receiver, path) {
561
- const measure = measureChecks(expression);
562
- if (measure.depth > MAX_CHECK_DEPTH ||
563
- measure.branches > MAX_BRANCHES ||
564
- measure.leaves > MAX_CHECK_LEAVES) {
565
- fail(path, 'Callable Policy checks exceed the V1 depth, branch, or leaf budget.');
566
- }
567
- const leaves = new Map();
568
- const visit = (value, pointer) => {
569
- if ('sameNode' in value)
570
- return;
571
- if (!('check' in value)) {
572
- const children = 'allOf' in value ? value.allOf : value.anyOf;
573
- children.forEach((child, index) => visit(child, `${pointer}/${index}`));
574
- return;
575
- }
576
- const contracts = callableObjectContracts(context, entry, input, receiver, value.object);
577
- if (contracts === undefined)
578
- return;
579
- const typings = new Map(contracts.map((contract) => [
580
- contract.kind === 'exact' ? Key.of(contract.ref) : 'default',
581
- typing.policyBranchTyping(value.check.name, contract),
582
- ]));
583
- const admitted = admittedCheckClasses(typings);
584
- if (admitted === undefined) {
585
- fail(pointer, 'Named Policy is incompatible with the callable object contract.');
586
- }
587
- leaves.set(value, { accepted: [...typings.keys()], admitted });
588
- };
589
- visit(expression, path);
590
- const [never] = neverAdmittedClasses(expression, leaves);
591
- if (never !== undefined) {
592
- fail(path, `Callable Policy checks can never admit ${never[0]} of Class ${never[1]}.`);
593
- }
594
- }
595
- function callableObjectContracts(context, entry, input, receiver, object) {
596
- if (object.kind === 'self') {
597
- return receiver === undefined ? undefined : [{ kind: 'exact', ref: receiver }];
598
- }
599
- if (object.kind === 'input') {
600
- return nodePathAccepts(entry, input, object.field).map((ref) => ({ kind: 'exact', ref }));
601
- }
602
- const ref = object.ref;
603
- if (ref.kind === 'core') {
604
- const node = context.core.get(Key.of(ref))?.value;
605
- return node === undefined ? undefined : [{ kind: 'exact', ref: node.class }];
606
- }
607
- const projection = ref.kind === 'class'
608
- ? 'Class'
609
- : ref.kind === 'function' || ref.kind === 'method'
610
- ? 'Function'
611
- : ref.kind === 'policy'
612
- ? 'Policy'
613
- : 'View';
614
- return [
615
- {
616
- kind: 'exact',
617
- ref: { origin: 'kernel.astrale.ai', kind: 'class', name: projection },
618
- },
619
- ];
620
- }
621
- function nodePathAccepts(entry, input, field) {
622
- const root = entry.values[input]?.nodes[entry.values[input].root]?.schema;
623
- const properties = root?.properties;
624
- const fieldSchema = properties?.[field];
625
- const annotation = fieldSchema?.['x-astrale-path'];
626
- return Array.isArray(annotation?.accepts)
627
- ? annotation.accepts
628
- : [];
629
- }
630
- /** Types one entry's Policies against the Node Classes of its own closure, as source admission. */
631
- class CompiledPolicyTyping {
632
- context;
633
- entry;
634
- normalized;
635
- allNodeClasses;
636
- concreteNodeClasses;
637
- constructor(context, entry, normalized, origins) {
638
- this.context = context;
639
- this.entry = entry;
640
- this.normalized = normalized;
641
- const nodeClasses = [...context.classes.values()].filter(({ value }) => value.kind === 'node' && origins.has(value.ref.origin));
642
- this.allNodeClasses = nodeClasses.map(({ value }) => value.ref);
643
- this.concreteNodeClasses = nodeClasses
644
- .filter(({ value }) => !value.abstract)
645
- .map(({ value }) => value.ref);
646
- }
647
- expandedBranches(name, visiting, namespace) {
648
- if (visiting.has(name))
649
- return undefined;
650
- const declaration = this.normalized.get(name);
651
- if (declaration === undefined)
652
- return undefined;
653
- if ('match' in declaration)
654
- return branchesOf(declaration.match, namespace);
655
- const nested = new Set(visiting);
656
- nested.add(name);
657
- const refs = 'allOf' in declaration ? declaration.allOf : declaration.anyOf;
658
- const children = [];
659
- for (const [index, ref] of refs.entries()) {
660
- if (ref.origin !== this.entry.origin || ref.kind !== 'policy')
661
- return undefined;
662
- const expanded = this.expandedBranches(ref.name, nested, `${namespace}/${index}:${ref.name}`);
663
- if (expanded === undefined)
664
- return undefined;
665
- children.push(expanded);
666
- }
667
- return 'allOf' in declaration ? branchProduct(children) : capBranches(children.flat());
668
- }
669
- policyAccepts(name, object) {
670
- return this.policyBranchTyping(name, object)?.every(Boolean) ?? false;
671
- }
672
- policyBranchTyping(name, object) {
673
- const branches = this.expandedBranches(name, new Set(), `$use:${name}`);
674
- return branches?.map((branch) => this.branchIsTyped(branch, { kind: 'object', object }));
675
- }
676
- policyAcceptsEdge(name, source, target) {
677
- const branches = this.expandedBranches(name, new Set(), `$edge-use:${name}`);
678
- return (branches !== undefined &&
679
- branches.every((branch) => this.branchIsTyped(branch, { kind: 'edge', source, target })));
680
- }
681
- concreteEndpointClasses(accepts) {
682
- return this.concreteNodeClasses.filter((candidate) => classSatisfiesAny(this.context, candidate, accepts));
683
- }
684
- endpointsOverlap(left, right) {
685
- const seed = [{ kind: 'open', requirements: [] }];
686
- return this.applyEndpoint(this.applyEndpoint(seed, left), right).length > 0;
687
- }
688
- branchIsTyped(branch, use) {
689
- if (targetMode(branch) !== use.kind)
690
- return false;
691
- return compiledEdgeAlternatives(branch.edges, this).some((variants) => this.constraintsSatisfiable(branch, variants, use));
692
- }
693
- constraintsSatisfiable(branch, variants, use) {
694
- const terms = new Set(use.kind === 'object' ? ['subject', 'object'] : ['subject', 'source', 'target']);
695
- for (const key of branch.variables.keys())
696
- terms.add(key);
697
- for (const key of branch.terms.keys())
698
- terms.add(key);
699
- for (const edge of branch.edges) {
700
- terms.add(termKey(edge.pattern.source, edge.namespace));
701
- terms.add(termKey(edge.pattern.target, edge.namespace));
702
- }
703
- const union = new CompiledUnionFind(terms);
704
- for (const [left, right] of branch.equalities)
705
- union.join(left, right);
706
- for (const variant of variants)
707
- for (const [left, right] of variant.equalities)
708
- union.join(left, right);
709
- const constraints = new Map();
710
- for (const variant of variants) {
711
- for (const [term, accepts] of variant.constraints) {
712
- const root = union.find(term);
713
- const values = constraints.get(root) ?? [];
714
- values.push(accepts);
715
- constraints.set(root, values);
716
- }
717
- }
718
- const groups = new Map();
719
- for (const term of terms) {
720
- const root = union.find(term);
721
- const values = groups.get(root) ?? [];
722
- values.push(term);
723
- groups.set(root, values);
724
- }
725
- for (const [root, members] of groups) {
726
- let candidates = [{ kind: 'open', requirements: [] }];
727
- for (const member of members) {
728
- candidates = this.applyInitial(candidates, member, branch, use);
729
- if (candidates.length === 0)
730
- return false;
731
- }
732
- for (const endpoint of constraints.get(root) ?? []) {
733
- candidates = this.applyEndpoint(candidates, endpoint);
734
- if (candidates.length === 0)
735
- return false;
736
- }
737
- }
738
- return true;
739
- }
740
- applyInitial(candidates, term, branch, use) {
741
- if (term === 'subject')
742
- return this.applyAnyNodeClass(candidates);
743
- const contract = compiledTargetContract(term, use);
744
- if (contract !== undefined) {
745
- return contract.kind === 'exact'
746
- ? this.applyExact(candidates, contract.ref)
747
- : uniqueCompiledCandidates(this.allNodeClasses.flatMap((ref) => this.applyExact(candidates, ref)));
748
- }
749
- const referenced = branch.terms.get(term);
750
- if (referenced?.kind === 'ref') {
751
- const contract = callableObjectContracts(this.context, this.entry, 0, undefined, referenced)?.[0];
752
- return contract?.kind === 'exact' ? this.applyExact(candidates, contract.ref) : [];
753
- }
754
- const selector = branch.variables.get(term);
755
- if (selector === undefined)
756
- return [];
757
- if (selector.kind === 'exact')
758
- return this.applyExact(candidates, selector.class);
759
- if (selector.kind === 'any')
760
- return candidates;
761
- return this.applyRequirement(candidates, selector.class);
762
- }
763
- applyExact(candidates, exact) {
764
- return uniqueCompiledCandidates(candidates.flatMap((candidate) => {
765
- if (candidate.kind === 'exact')
766
- return Key.of(candidate.ref) === Key.of(exact) ? [candidate] : [];
767
- return candidate.requirements.every((required) => classSatisfiesAny(this.context, exact, [required]))
768
- ? [{ kind: 'exact', ref: exact }]
769
- : [];
770
- }));
771
- }
772
- applyAnyNodeClass(candidates) {
773
- return uniqueCompiledCandidates(candidates.flatMap((candidate) => candidate.kind === 'exact'
774
- ? [candidate]
775
- : this.allNodeClasses
776
- .filter((ref) => candidate.requirements.every((required) => classSatisfiesAny(this.context, ref, [required])))
777
- .map((ref) => ({ kind: 'exact', ref }))));
778
- }
779
- applyRequirement(candidates, required) {
780
- const result = [];
781
- for (const candidate of candidates) {
782
- if (candidate.kind === 'exact') {
783
- if (classSatisfiesAny(this.context, candidate.ref, [required]))
784
- result.push(candidate);
785
- }
786
- else {
787
- result.push({
788
- kind: 'open',
789
- requirements: uniqueClassRefs([...candidate.requirements, required]),
790
- });
791
- }
792
- }
793
- return uniqueCompiledCandidates(result);
794
- }
795
- applyEndpoint(candidates, accepts) {
796
- const result = [];
797
- for (const candidate of candidates) {
798
- if (candidate.kind === 'exact') {
799
- if (classSatisfiesAny(this.context, candidate.ref, accepts))
800
- result.push(candidate);
801
- }
802
- else {
803
- for (const accept of accepts) {
804
- result.push({
805
- kind: 'open',
806
- requirements: uniqueClassRefs([...candidate.requirements, accept]),
807
- });
808
- }
809
- }
810
- }
811
- return uniqueCompiledCandidates(result);
812
- }
813
- }
814
- function compiledTargetContract(term, use) {
815
- if (use.kind === 'object')
816
- return term === 'object' ? use.object : undefined;
817
- return term === 'source' ? use.source : term === 'target' ? use.target : undefined;
818
- }
819
- function compiledEdgeAlternatives(edges, typing) {
820
- let result = [[]];
821
- for (const edge of edges) {
822
- const variants = compiledVariantsForEdge(edge, typing);
823
- if (variants.length === 0)
824
- return [];
825
- result = result.flatMap((prefix) => variants.map((variant) => [...prefix, variant]));
826
- }
827
- return result;
828
- }
829
- function compiledVariantsForEdge(use, typing) {
830
- const declaration = typing.context.classes.get(Key.of(use.pattern.class))?.value;
831
- if (declaration === undefined || !isCompiledEdge(declaration))
832
- return [];
833
- const endpoints = declaration.orientation === 'directed'
834
- ? [declaration.endpoints.source, declaration.endpoints.target]
835
- : declaration.endpoints;
836
- const repeat = use.pattern.repeat ?? { min: 1, max: 1 };
837
- const sourceKey = termKey(use.pattern.source, use.namespace);
838
- const targetKey = termKey(use.pattern.target, use.namespace);
839
- const variants = [];
840
- for (let length = repeat.min; length <= repeat.max; length++) {
841
- if (length === 0) {
842
- variants.push({ constraints: [], equalities: [[sourceKey, targetKey]] });
843
- continue;
844
- }
845
- const orientationCount = declaration.orientation === 'undirected' ? 2 ** length : 1;
846
- for (let mask = 0; mask < orientationCount; mask++) {
847
- const reversed = Array.from({ length }, (_, index) => declaration.orientation === 'undirected' && (mask & (1 << index)) !== 0);
848
- let valid = true;
849
- for (let index = 0; index + 1 < length; index++) {
850
- const incoming = reversed[index] ? endpoints[0] : endpoints[1];
851
- const outgoing = reversed[index + 1] ? endpoints[1] : endpoints[0];
852
- if (!typing.endpointsOverlap(incoming.accepts, outgoing.accepts))
853
- valid = false;
854
- }
855
- if (!valid)
856
- continue;
857
- const first = reversed[0] ? endpoints[1] : endpoints[0];
858
- const last = reversed[length - 1] ? endpoints[0] : endpoints[1];
859
- variants.push({
860
- constraints: [
861
- [sourceKey, first.accepts],
862
- [targetKey, last.accepts],
863
- ],
864
- equalities: [],
865
- });
866
- }
867
- }
868
- return uniqueCompiledEdgeVariants(variants);
869
- }
870
- function uniqueCompiledEdgeVariants(values) {
871
- const result = new Map();
872
- for (const value of values) {
873
- const key = `${value.constraints.map(([term, accepts]) => `${term}:${accepts.map(Key.of).sort(compareUnicode).join(',')}`).join('|')}=${value.equalities.map(([left, right]) => `${left}:${right}`).join('|')}`;
874
- result.set(key, value);
875
- }
876
- return [...result.values()];
877
- }
878
- function uniqueCompiledCandidates(values) {
879
- const result = new Map();
880
- for (const value of values) {
881
- const key = value.kind === 'exact'
882
- ? `exact:${Key.of(value.ref)}`
883
- : `open:${value.requirements.map(Key.of).sort(compareUnicode).join('|')}`;
884
- result.set(key, value);
885
- }
886
- return [...result.values()];
887
- }
888
- function uniqueClassRefs(values) {
889
- return [...new Map(values.map((value) => [Key.of(value), value])).values()];
890
- }
891
- function measureCompiledPolicy(origin, name, normalized, visiting) {
892
- if (visiting.has(name))
893
- return undefined;
894
- const expression = normalized.get(name);
895
- if (expression === undefined)
896
- return undefined;
897
- if ('match' in expression)
898
- return measureCompiledPattern(expression.match, origin);
899
- const nested = new Set(visiting);
900
- nested.add(name);
901
- const refs = 'allOf' in expression ? expression.allOf : expression.anyOf;
902
- const children = refs.map((ref) => ref.origin === origin ? measureCompiledPolicy(origin, ref.name, normalized, nested) : undefined);
903
- if (children.some((value) => value === undefined))
904
- return undefined;
905
- return combineCompiledMeasures('allOf' in expression ? 'allOf' : 'anyOf', children);
906
- }
907
- function measureCompiledPattern(pattern, origin) {
908
- if ('sameNode' in pattern)
909
- return {
910
- depth: 1,
911
- edgeCounts: [1],
912
- domains: new Set([
913
- origin,
914
- ...[pattern.sameNode.left, pattern.sameNode.right].flatMap((term) => term.kind === 'ref' ? [policyObjectOrigin(term.ref)] : []),
915
- ]),
916
- };
917
- if ('source' in pattern)
918
- return {
919
- depth: 1,
920
- edgeCounts: [1],
921
- domains: new Set([
922
- origin,
923
- pattern.class.origin,
924
- ...[pattern.source, pattern.target].flatMap((term) => term.kind === 'ref' ? [policyObjectOrigin(term.ref)] : []),
925
- ]),
926
- };
927
- if ('exists' in pattern) {
928
- const child = measureCompiledPattern(pattern.exists.where, origin);
929
- return {
930
- depth: child.depth + 1,
931
- edgeCounts: child.edgeCounts,
932
- domains: new Set([
933
- ...child.domains,
934
- ...pattern.exists.nodes.flatMap((node) => {
935
- const ref = policyVariableClass(node);
936
- return ref === undefined ? [] : [ref.origin];
937
- }),
938
- ]),
939
- };
940
- }
941
- return combineCompiledMeasures('allOf' in pattern ? 'allOf' : 'anyOf', ('allOf' in pattern ? pattern.allOf : pattern.anyOf).map((child) => measureCompiledPattern(child, origin)));
942
- }
943
- function combineCompiledMeasures(kind, children) {
944
- const depth = 1 + Math.max(0, ...children.map((child) => child.depth));
945
- const domains = new Set(children.flatMap((child) => [...child.domains]));
946
- if (kind === 'anyOf')
947
- return {
948
- depth,
949
- edgeCounts: children.flatMap((child) => child.edgeCounts).slice(0, BRANCH_OVERFLOW),
950
- domains,
951
- };
952
- let edgeCounts = [0];
953
- for (const child of children) {
954
- const next = [];
955
- for (const left of edgeCounts) {
956
- for (const right of child.edgeCounts) {
957
- next.push(left + right);
958
- if (next.length >= BRANCH_OVERFLOW)
959
- break;
960
- }
961
- if (next.length >= BRANCH_OVERFLOW)
962
- break;
963
- }
964
- edgeCounts = next;
965
- }
966
- return { depth, edgeCounts, domains };
967
- }
968
- class CompiledUnionFind {
969
- #parents = new Map();
970
- constructor(values) {
971
- for (const value of values)
972
- this.add(value);
973
- }
974
- add(value) {
975
- if (!this.#parents.has(value))
976
- this.#parents.set(value, value);
977
- }
978
- find(value) {
979
- this.add(value);
980
- const parent = this.#parents.get(value);
981
- if (parent === value)
982
- return value;
983
- const root = this.find(parent);
984
- this.#parents.set(value, root);
985
- return root;
986
- }
987
- join(left, right) {
988
- const a = this.find(left);
989
- const b = this.find(right);
990
- if (a !== b)
991
- this.#parents.set(b, a);
992
- }
993
- }
994
- function validateCore(candidate, context) {
995
- for (const [entryIndex, entry] of candidate.closure.entries()) {
996
- const nodes = new Map();
997
- for (const [index, value] of entry.definitions.core.entries()) {
998
- const path = `/closure/${entryIndex}/definitions/core/${index}`;
999
- const classValue = context.classes.get(Key.of(value.class));
1000
- if (value.class.origin !== entry.origin ||
1001
- classValue?.value.kind !== 'node' ||
1002
- classValue.value.abstract)
1003
- fail(`${path}/class`, 'A Core Node requires a local concrete Node Class.');
1004
- const analysis = context.analyses.get(Key.of(value.class));
1005
- if (analysis === undefined)
1006
- fail(`${path}/class`, 'Core Node Class analysis is absent.');
1007
- validateCoreProperties(value.properties, analysis, `${path}/properties`);
1008
- nodes.set(Key.of(value.ref), {
1009
- key: Key.of(value.ref),
1010
- value,
1011
- class: classValue,
1012
- analysis,
1013
- pointer: path,
1014
- });
1015
- }
1016
- const edges = [];
1017
- for (const [index, value] of entry.facts.core.entries()) {
1018
- const path = `/closure/${entryIndex}/facts/core/${index}`;
1019
- const classValue = context.classes.get(Key.of(value.class));
1020
- if (value.class.origin !== entry.origin ||
1021
- classValue === undefined ||
1022
- !isCompiledEdge(classValue.value) ||
1023
- classValue.value.abstract)
1024
- fail(`${path}/class`, 'A Core Edge requires a local concrete Edge Class.');
1025
- const analysis = context.analyses.get(Key.of(value.class));
1026
- if (analysis === undefined)
1027
- fail(`${path}/class`, 'Core Edge Class analysis is absent.');
1028
- validateCoreProperties(value.properties, analysis, `${path}/properties`);
1029
- const source = resolveCoreEndpoint(context, entry, value.source, nodes, `${path}/source`);
1030
- const target = resolveCoreEndpoint(context, entry, value.target, nodes, `${path}/target`);
1031
- const declaration = classValue.value;
1032
- const satisfies = declaration.orientation === 'directed'
1033
- ? endpointSatisfies(context, source, declaration.endpoints.source.accepts) &&
1034
- endpointSatisfies(context, target, declaration.endpoints.target.accepts)
1035
- : (endpointSatisfies(context, source, declaration.endpoints[0].accepts) &&
1036
- endpointSatisfies(context, target, declaration.endpoints[1].accepts)) ||
1037
- (endpointSatisfies(context, source, declaration.endpoints[1].accepts) &&
1038
- endpointSatisfies(context, target, declaration.endpoints[0].accepts));
1039
- if (!satisfies)
1040
- fail(path, 'Core Edge endpoints do not satisfy its exact Class.');
1041
- edges.push({
1042
- source,
1043
- target,
1044
- value,
1045
- class: classValue,
1046
- pointer: path,
1047
- });
1048
- }
1049
- validateCoreEdgeSet(context, entry, entryIndex, nodes, edges);
1050
- }
1051
- }
1052
- function validateCoreProperties(values, analysis, path) {
1053
- for (const key of Object.keys(values))
1054
- if (!analysis.properties.has(key))
1055
- fail(`${path}/${escapePointer(key)}`, 'Core properties contain an unknown Property key.');
1056
- for (const property of analysis.properties.values()) {
1057
- if (!Object.hasOwn(values, property.key)) {
1058
- if (property.declaration.required && property.declaration.auto === undefined)
1059
- fail(`${path}/${escapePointer(property.key)}`, 'Core properties omit a required Property.');
1060
- continue;
1061
- }
1062
- if (property.declaration.auto !== undefined)
1063
- fail(`${path}/${escapePointer(property.key)}`, 'Core properties cannot author an automatic Property.');
1064
- if (validateValueProgram(bindValueProgram(property.entry.values[property.declaration.value], property.entry.patterns), values[property.key]).length > 0)
1065
- fail(`${path}/${escapePointer(property.key)}`, 'Core Property value does not satisfy its exact contract.');
1066
- }
1067
- }
1068
- function resolveCoreEndpoint(context, entry, ref, nodes, path) {
1069
- if (ref.kind === 'domain')
1070
- return { key: 'domain' };
1071
- const undeclared = () => fail(path, 'Core endpoint does not name a declared local Definition.');
1072
- if (policyObjectOrigin(ref) !== entry.origin)
1073
- return undeclared();
1074
- if (ref.kind === 'core')
1075
- return nodes.get(Key.of(ref)) ?? undeclared();
1076
- if (!declaresProjectedMember(context, entry, ref))
1077
- return undeclared();
1078
- return { key: policyObjectKey(ref), class: projectedMemberClass(ref), pointer: path };
1079
- }
1080
- /** Only a projected declaration can carry a Core fact; an abstract Method is never projected. */
1081
- function declaresProjectedMember(context, entry, ref) {
1082
- if (ref.kind === 'method') {
1083
- const owner = context.classes.get(Key.of(ref.owner))?.value;
1084
- const method = owner?.kind === 'node' ? owner.methods.find((value) => value.name === ref.name) : undefined;
1085
- return method !== undefined && isMethodExecutable(owner, method);
1086
- }
1087
- const declarations = ref.kind === 'class'
1088
- ? entry.definitions.classes
1089
- : ref.kind === 'function'
1090
- ? entry.definitions.functions
1091
- : ref.kind === 'policy'
1092
- ? entry.definitions.policies
1093
- : entry.definitions.views;
1094
- return declarations.some((declaration) => declaration.ref.name === ref.name);
1095
- }
1096
- function endpointSatisfies(context, endpoint, accepts) {
1097
- if ('value' in endpoint)
1098
- return classSatisfiesAny(context, endpoint.value.class, accepts);
1099
- if ('class' in endpoint)
1100
- return classSatisfiesAny(context, endpoint.class, accepts);
1101
- return true;
1102
- }
1103
- function validateCoreEdgeSet(context, entry, entryIndex, nodes, edges) {
1104
- const seen = new Set();
1105
- const slices = new Map();
1106
- for (const edge of edges) {
1107
- const classKey = Key.of(edge.value.class);
1108
- const [source, target] = edge.class.value.orientation === 'undirected' &&
1109
- compareUnicode(edge.source.key, edge.target.key) > 0
1110
- ? [edge.target.key, edge.source.key]
1111
- : [edge.source.key, edge.target.key];
1112
- const key = `${classKey}\u0000${source}\u0000${target}`;
1113
- if (seen.has(key))
1114
- fail(edge.pointer, 'Core Edge duplicates an exact normalized Edge identity.');
1115
- seen.add(key);
1116
- const values = slices.get(classKey) ?? [];
1117
- values.push(edge);
1118
- slices.set(classKey, values);
1119
- if (edge.class.value.constraints.noSelf === true && edge.source.key === edge.target.key)
1120
- fail(edge.pointer, 'Core Edge violates noSelf.');
1121
- }
1122
- for (const edgeClass of entry.definitions.classes) {
1123
- if (!isCompiledEdge(edgeClass))
1124
- continue;
1125
- const classEdges = slices.get(Key.of(edgeClass.ref)) ?? [];
1126
- if (edgeClass.constraints.acyclic === true && hasCycle(classEdges))
1127
- fail(`/closure/${entryIndex}/facts/core`, 'Core Edge set violates exact-Class acyclicity.');
1128
- validateCardinality(context, edgeClass, classEdges, nodes);
1129
- }
1130
- }
1131
- function validateCardinality(context, declaration, edges, nodes) {
1132
- for (const subject of cardinalitySubjects(edges, nodes)) {
1133
- if (declaration.orientation === 'directed') {
1134
- if (classSatisfiesAny(context, subject.class, declaration.endpoints.source.accepts))
1135
- checkCount(declaration.endpoints.source.outgoing, edges.filter(({ source }) => source.key === subject.key).length, subject.pointer, subject.minimum);
1136
- if (classSatisfiesAny(context, subject.class, declaration.endpoints.target.accepts))
1137
- checkCount(declaration.endpoints.target.incoming, edges.filter(({ target }) => target.key === subject.key).length, subject.pointer, subject.minimum);
1138
- }
1139
- else {
1140
- const degree = edges.filter(({ source, target }) => source.key === subject.key || target.key === subject.key).length;
1141
- const [first, second] = declaration.endpoints;
1142
- if (classSatisfiesAny(context, subject.class, first.accepts))
1143
- checkCount(first.incident, degree, subject.pointer, subject.minimum);
1144
- if (!sameIncidentEndpoint(first, second) &&
1145
- classSatisfiesAny(context, subject.class, second.accepts))
1146
- checkCount(second.incident, degree, subject.pointer, subject.minimum);
1147
- }
1148
- }
1149
- }
1150
- /**
1151
- * Every declared Core Node proves its own complete degree, so both bounds hold. A Definition
1152
- * endpoint is only named by the Edges that reach it: the Domain may also create ordinary Edges of
1153
- * the same Class against it, so a minimum is unprovable here and only the maximum is enforced.
1154
- */
1155
- function cardinalitySubjects(edges, nodes) {
1156
- const subjects = new Map();
1157
- for (const node of nodes.values()) {
1158
- subjects.set(node.key, {
1159
- key: node.key,
1160
- class: node.value.class,
1161
- pointer: node.pointer,
1162
- minimum: true,
1163
- });
1164
- }
1165
- for (const edge of edges) {
1166
- for (const endpoint of [edge.source, edge.target]) {
1167
- if ('value' in endpoint || !('class' in endpoint) || subjects.has(endpoint.key))
1168
- continue;
1169
- subjects.set(endpoint.key, {
1170
- key: endpoint.key,
1171
- class: endpoint.class,
1172
- pointer: endpoint.pointer,
1173
- minimum: false,
1174
- });
1175
- }
1176
- }
1177
- return Object.freeze([...subjects.values()]);
1178
- }
1179
- function checkCount(count, degree, path, enforceMinimum) {
1180
- const [minimum, maximum] = countBounds(count);
1181
- if (degree > maximum || (degree < minimum && enforceMinimum))
1182
- fail(path, `Core degree ${degree} violates Edge count ${count}.`);
1183
- }
1184
- function countBounds(value) {
1185
- switch (value) {
1186
- case '0..*':
1187
- return [0, Number.POSITIVE_INFINITY];
1188
- case '1..*':
1189
- return [1, Number.POSITIVE_INFINITY];
1190
- case '0..1':
1191
- return [0, 1];
1192
- case '1':
1193
- return [1, 1];
1194
- }
1195
- }
1196
- function hasCycle(edges) {
1197
- const outgoing = new Map();
1198
- for (const edge of edges) {
1199
- const values = outgoing.get(edge.source.key) ?? [];
1200
- values.push(edge.target.key);
1201
- outgoing.set(edge.source.key, values);
1202
- }
1203
- const visiting = new Set();
1204
- const visited = new Set();
1205
- const visit = (key) => {
1206
- if (visiting.has(key))
1207
- return true;
1208
- if (visited.has(key))
1209
- return false;
1210
- visiting.add(key);
1211
- if ((outgoing.get(key) ?? []).some(visit))
1212
- return true;
1213
- visiting.delete(key);
1214
- visited.add(key);
1215
- return false;
1216
- };
1217
- return [...outgoing.keys()].some(visit);
1218
- }
1219
- function escapePointer(value) {
1220
- return value.replaceAll('~', '~0').replaceAll('/', '~1');
1221
- }
1222
- function isCompiledEdge(value) {
1223
- return value.kind === 'edge';
1224
- }
1225
- function fail(pointer, message) {
1226
- return invalidCompiled('COMPILED_SOURCE_INVALID', message, pointer);
1227
- }