@astrale-os/kernel-dsl 1.0.0-beta.11 → 1.0.0-beta.13

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 (167) hide show
  1. package/dist/v1/addressing/definition-ref.js +21 -37
  2. package/dist/v1/addressing/ordering.d.ts +2 -0
  3. package/dist/v1/addressing/ordering.js +4 -0
  4. package/dist/v1/addressing/patterns.d.ts +0 -1
  5. package/dist/v1/addressing/patterns.js +0 -5
  6. package/dist/v1/builder/callable/authoring.d.ts +1 -1
  7. package/dist/v1/builder/class/authoring.d.ts +6 -256
  8. package/dist/v1/builder/class/authoring.js +5 -28
  9. package/dist/v1/builder/class/handle.d.ts +27 -0
  10. package/dist/v1/builder/class/handle.js +1 -0
  11. package/dist/v1/builder/class/input.d.ts +129 -0
  12. package/dist/v1/builder/class/projection.d.ts +129 -0
  13. package/dist/v1/builder/class/projection.js +1 -0
  14. package/dist/v1/builder/core/authoring.d.ts +1 -1
  15. package/dist/v1/builder/index.d.ts +3 -1
  16. package/dist/v1/builder/lowering/class.d.ts +1 -1
  17. package/dist/v1/builder/lowering/schema/context.d.ts +2 -1
  18. package/dist/v1/builder/lowering/schema/context.js +2 -4
  19. package/dist/v1/builder/lowering/value/path.d.ts +1 -1
  20. package/dist/v1/builder/lowering/value/path.js +2 -4
  21. package/dist/v1/builder/lowering/value/zod.js +1 -3
  22. package/dist/v1/builder/policy/authoring.d.ts +1 -1
  23. package/dist/v1/builder/schema/define.d.ts +0 -1
  24. package/dist/v1/builder/schema/diagnostic.d.ts +2 -1
  25. package/dist/v1/builder/schema/diagnostic.js +5 -10
  26. package/dist/v1/builder/schema/input.d.ts +1 -1
  27. package/dist/v1/builder/schema/projection/class.d.ts +2 -1
  28. package/dist/v1/builder/schema/projection/core-input.d.ts +2 -1
  29. package/dist/v1/builder/schema/projection/identity.d.ts +2 -1
  30. package/dist/v1/builder/schema/projection/property.d.ts +1 -1
  31. package/dist/v1/builder/schema/projection.d.ts +1 -1
  32. package/dist/v1/builder/value/full-match.d.ts +0 -1
  33. package/dist/v1/builder/value/full-match.js +0 -1
  34. package/dist/v1/builder/value/path-metadata.d.ts +1 -1
  35. package/dist/v1/builder/value/path.d.ts +1 -1
  36. package/dist/v1/bundle/codec.js +2 -4
  37. package/dist/v1/bundle/diagnostic.js +3 -7
  38. package/dist/v1/bundle/structure.js +2 -4
  39. package/dist/v1/compiled/admission/diagnostic.d.ts +1 -1
  40. package/dist/v1/compiled/admission/diagnostic.js +1 -3
  41. package/dist/v1/compiled/admission/revision.d.ts +9 -1
  42. package/dist/v1/compiled/admission/revision.js +6 -3
  43. package/dist/v1/compiled/admission/semantics/class.d.ts +4 -1
  44. package/dist/v1/compiled/admission/semantics/class.js +4 -8
  45. package/dist/v1/compiled/admission/semantics/core.d.ts +1 -24
  46. package/dist/v1/compiled/admission/semantics/core.js +54 -34
  47. package/dist/v1/compiled/admission/semantics/edge.d.ts +2 -4
  48. package/dist/v1/compiled/admission/semantics/edge.js +6 -65
  49. package/dist/v1/compiled/admission/semantics/policy/callable.js +0 -2
  50. package/dist/v1/compiled/admission/semantics/policy/contracts.d.ts +5 -20
  51. package/dist/v1/compiled/admission/semantics/policy/contracts.js +8 -21
  52. package/dist/v1/compiled/admission/semantics/policy/parity.js +13 -22
  53. package/dist/v1/compiled/admission/semantics/policy/pattern.d.ts +4 -1
  54. package/dist/v1/compiled/admission/semantics/policy/pattern.js +4 -16
  55. package/dist/v1/compiled/admission/semantics/policy/typing.d.ts +2 -24
  56. package/dist/v1/compiled/admission/semantics/policy/typing.js +19 -201
  57. package/dist/v1/compiled/compiler/compile.js +6 -8
  58. package/dist/v1/compiled/diagnostic.js +2 -3
  59. package/dist/v1/compiled/loader/load.d.ts +3 -2
  60. package/dist/v1/compiled/loader/load.js +10 -44
  61. package/dist/v1/domain/create.js +3 -5
  62. package/dist/v1/domain/namespace/member-collection.js +4 -6
  63. package/dist/v1/domain/validation/create.d.ts +0 -1
  64. package/dist/v1/domain/validation/create.js +0 -35
  65. package/dist/v1/language/class/inheritance.d.ts +18 -0
  66. package/dist/v1/language/class/inheritance.js +73 -0
  67. package/dist/v1/language/class/property-values.js +2 -4
  68. package/dist/v1/language/core/facts.d.ts +72 -0
  69. package/dist/v1/language/core/facts.js +95 -0
  70. package/dist/v1/language/index.d.ts +1 -0
  71. package/dist/v1/language/index.js +1 -0
  72. package/dist/v1/language/policy/analysis/branches.d.ts +8 -0
  73. package/dist/v1/language/policy/analysis/branches.js +2 -1
  74. package/dist/v1/language/policy/analysis/budgets.d.ts +13 -1
  75. package/dist/v1/language/policy/analysis/budgets.js +104 -0
  76. package/dist/v1/language/policy/analysis/typing.d.ts +48 -0
  77. package/dist/v1/language/policy/analysis/typing.js +338 -0
  78. package/dist/v1/language/validation/diagnostic.d.ts +15 -1
  79. package/dist/v1/language/validation/diagnostic.js +21 -0
  80. package/dist/v1/language/value/compile/fields.js +4 -8
  81. package/dist/v1/language/value/inclusion/arrays.d.ts +3 -0
  82. package/dist/v1/language/value/inclusion/arrays.js +85 -0
  83. package/dist/v1/language/value/inclusion/assertions.d.ts +18 -0
  84. package/dist/v1/language/value/inclusion/assertions.js +29 -0
  85. package/dist/v1/language/value/inclusion/combinators.d.ts +3 -0
  86. package/dist/v1/language/value/inclusion/combinators.js +67 -0
  87. package/dist/v1/language/value/inclusion/equivalence.d.ts +4 -0
  88. package/dist/v1/language/value/inclusion/equivalence.js +74 -0
  89. package/dist/v1/language/value/inclusion/evidence.d.ts +34 -0
  90. package/dist/v1/language/value/inclusion/evidence.js +138 -0
  91. package/dist/v1/language/value/inclusion/includes.d.ts +17 -0
  92. package/dist/v1/language/value/inclusion/includes.js +25 -719
  93. package/dist/v1/language/value/inclusion/keywords.d.ts +5 -0
  94. package/dist/v1/language/value/inclusion/keywords.js +31 -0
  95. package/dist/v1/language/value/inclusion/kinds.d.ts +17 -0
  96. package/dist/v1/language/value/inclusion/kinds.js +57 -0
  97. package/dist/v1/language/value/inclusion/location.d.ts +24 -0
  98. package/dist/v1/language/value/inclusion/location.js +63 -0
  99. package/dist/v1/language/value/inclusion/numbers.d.ts +3 -0
  100. package/dist/v1/language/value/inclusion/numbers.js +41 -0
  101. package/dist/v1/language/value/inclusion/objects.d.ts +3 -0
  102. package/dist/v1/language/value/inclusion/objects.js +190 -0
  103. package/dist/v1/language/value/inclusion/strings.d.ts +3 -0
  104. package/dist/v1/language/value/inclusion/strings.js +29 -0
  105. package/dist/v1/language/value/inclusion/types.d.ts +3 -0
  106. package/dist/v1/language/value/inclusion/types.js +11 -0
  107. package/dist/v1/language/value/ordering.d.ts +9 -0
  108. package/dist/v1/language/value/ordering.js +72 -9
  109. package/dist/v1/language/value/profile/rules.js +5 -7
  110. package/dist/v1/language/value/profile/validate.js +1 -10
  111. package/dist/v1/language/value/reference/recursion.js +2 -22
  112. package/dist/v1/language/value/syntax/pointer.d.ts +2 -0
  113. package/dist/v1/language/value/syntax/pointer.js +5 -2
  114. package/dist/v1/language/value/syntax/walk.d.ts +2 -0
  115. package/dist/v1/language/value/syntax/walk.js +19 -20
  116. package/dist/v1/schema/compatibility/diff.js +2 -4
  117. package/dist/v1/schema/compatibility/meaning/footprint.js +16 -50
  118. package/dist/v1/schema/compatibility/structure/requirements.js +26 -58
  119. package/dist/v1/schema/compatibility/walk.d.ts +17 -0
  120. package/dist/v1/schema/compatibility/walk.js +41 -0
  121. package/dist/v1/schema/diagnostic.js +3 -11
  122. package/dist/v1/schema/resolution/context.js +3 -5
  123. package/dist/v1/schema/validation/callable.js +3 -5
  124. package/dist/v1/schema/validation/class.js +10 -52
  125. package/dist/v1/schema/validation/core.js +65 -199
  126. package/dist/v1/schema/validation/domain.js +1 -10
  127. package/dist/v1/schema/validation/domain.structure.js +2 -4
  128. package/dist/v1/schema/validation/policy/budgets.js +5 -112
  129. package/dist/v1/schema/validation/policy/context.d.ts +5 -3
  130. package/dist/v1/schema/validation/policy/context.js +5 -3
  131. package/dist/v1/schema/validation/policy/objects.d.ts +1 -1
  132. package/dist/v1/schema/validation/policy/objects.js +6 -14
  133. package/dist/v1/schema/validation/policy/references.js +5 -6
  134. package/dist/v1/schema/validation/policy/typing.d.ts +3 -18
  135. package/dist/v1/schema/validation/policy/typing.js +22 -356
  136. package/dist/v1/schema/validation/policy/uses.d.ts +1 -1
  137. package/dist/v1/schema/validation/policy/uses.js +9 -22
  138. package/dist/v1/schema/validation/policy/validate.js +3 -2
  139. package/dist/v1/schema/validation/value-api.d.ts +9 -0
  140. package/dist/v1/schema/validation/value-api.js +23 -1
  141. package/dist/v1/schema/validation/value.js +5 -7
  142. package/dist/v1/schema/validation/view.js +4 -8
  143. package/dist/value/canonical.d.ts +3 -2
  144. package/dist/value/canonical.js +30 -6
  145. package/dist/value/decode.d.ts +7 -0
  146. package/dist/value/decode.js +159 -9
  147. package/dist/value/index.d.ts +1 -1
  148. package/dist/value/json.d.ts +1 -1
  149. package/dist/value/json.js +1 -1
  150. package/dist/value/types.d.ts +6 -0
  151. package/dist/value/unicode.d.ts +2 -1
  152. package/dist/value/unicode.js +2 -0
  153. package/package.json +1 -1
  154. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +0 -5
  155. package/dist/v1/compiled/admission/semantics/cardinality.js +0 -128
  156. package/dist/v1/compiled/admission/semantics/policy/equality.d.ts +0 -8
  157. package/dist/v1/compiled/admission/semantics/policy/equality.js +0 -27
  158. package/dist/v1/compiled/admission/semantics/policy/measure.d.ts +0 -9
  159. package/dist/v1/compiled/admission/semantics/policy/measure.js +0 -81
  160. package/dist/v1/compiled/admission/semantics/policy/variants.d.ts +0 -10
  161. package/dist/v1/compiled/admission/semantics/policy/variants.js +0 -65
  162. package/dist/v1/language/class/index.d.ts +0 -8
  163. package/dist/v1/language/class/index.js +0 -1
  164. package/dist/v1/language/core/index.d.ts +0 -2
  165. package/dist/v1/language/value/index.d.ts +0 -11
  166. package/dist/v1/language/value/index.js +0 -8
  167. /package/dist/v1/{language/core/index.js → builder/class/input.js} +0 -0
@@ -0,0 +1,338 @@
1
+ import { Key } from '../../../addressing/index.js';
2
+ import { definitionRefIndex } from '../../../addressing/ordering.js';
3
+ import { branchesOf, branchProduct, capBranches, targetMode, termKey, UnionFind, } from './branches.js';
4
+ import { MAX_REPEAT } from './budgets.js';
5
+ import { admittedCheckClasses } from './checks.js';
6
+ export class PolicyTyping {
7
+ scope;
8
+ // Expanded branches of each named Policy, cached by node identity. A Policy's expansion is reused
9
+ // across every composition path that reaches it (and across every use site and object contract,
10
+ // none of which the expansion depends on), so a shared-child composition DAG is expanded in work
11
+ // linear in the number of Policies instead of enumerating every path. The expansion result is
12
+ // independent of the reaching path up to the opaque term-namespace labels, which only ever
13
+ // disambiguate variables within one branch and leave targetMode and branch typing unchanged.
14
+ // Two occurrences of one Policy in a branch share labels only when a Policy reaches it by two
15
+ // paths; for that to merge distinct variables, the Policy expands deeper than MAX_PATTERN_DEPTH.
16
+ #expansions = new Map();
17
+ constructor(scope) {
18
+ this.scope = scope;
19
+ }
20
+ /** The normalized branches of a named Policy, `undefined` when it does not expand. */
21
+ expandedBranches(name, namespace) {
22
+ return this.#expandedBranches(name, new Set(), namespace);
23
+ }
24
+ policyAccepts(name, object) {
25
+ return this.#policyBranchTyping(name, object)?.every(Boolean) ?? false;
26
+ }
27
+ /** Whether the Policy accepts every concrete source and target pair the Edge Class accepts. */
28
+ policyAcceptsEdgeClass(name, declaration) {
29
+ const endpoints = declaration.orientation === 'directed'
30
+ ? [declaration.endpoints.source, declaration.endpoints.target]
31
+ : declaration.endpoints;
32
+ const sources = this.#concreteEndpointClasses(endpoints[0].accepts);
33
+ const targets = this.#concreteEndpointClasses(endpoints[1].accepts);
34
+ return sources.every((source) => targets.every((target) => this.#policyAcceptsEdge(name, { kind: 'exact', ref: source }, { kind: 'exact', ref: target })));
35
+ }
36
+ /**
37
+ * How a check of the Policy types against each contract of its object, `undefined` when the
38
+ * Policy does not expand or one of its branches is typed for no contract.
39
+ */
40
+ checkLeafClasses(name, contracts) {
41
+ const typings = new Map(contracts.map((contract) => [
42
+ contract.kind === 'exact' ? Key.of(contract.ref) : 'default',
43
+ this.#policyBranchTyping(name, contract),
44
+ ]));
45
+ const admitted = admittedCheckClasses(typings);
46
+ return admitted === undefined ? undefined : { accepted: [...typings.keys()], admitted };
47
+ }
48
+ endpointsOverlap(left, right) {
49
+ const seed = [{ kind: 'open', requirements: [] }];
50
+ const afterLeft = this.#applyEndpoint(seed, left);
51
+ return this.#applyEndpoint(afterLeft, right).length > 0;
52
+ }
53
+ branchIsTyped(branch, use) {
54
+ if (targetMode(branch) !== use.kind)
55
+ return false;
56
+ const alternatives = edgeAlternatives(branch.edges, this);
57
+ return alternatives.some((variants) => this.#constraintsAreSatisfiable(branch, variants, use));
58
+ }
59
+ /** Whether each normalized branch of the Policy is typed for an object of this contract. */
60
+ #policyBranchTyping(name, object) {
61
+ const branches = this.#expandedBranches(name, new Set(), `$use:${name}`);
62
+ const use = { kind: 'object', object };
63
+ return branches?.map((branch) => this.branchIsTyped(branch, use));
64
+ }
65
+ #policyAcceptsEdge(name, source, target) {
66
+ const branches = this.#expandedBranches(name, new Set(), `$edge-use:${name}`);
67
+ const use = { kind: 'edge', source, target };
68
+ return branches !== undefined && branches.every((branch) => this.branchIsTyped(branch, use));
69
+ }
70
+ #concreteEndpointClasses(accepts) {
71
+ return this.scope.concreteNodeClasses.filter((candidate) => this.#classSatisfiesEndpoint(candidate, accepts));
72
+ }
73
+ #expandedBranches(name, visiting, namespace) {
74
+ if (visiting.has(name))
75
+ return undefined;
76
+ const cached = this.#expansions.get(name);
77
+ if (cached !== undefined || this.#expansions.has(name))
78
+ return cached;
79
+ const expanded = this.#expandDeclaration(name, visiting, namespace);
80
+ this.#expansions.set(name, expanded);
81
+ return expanded;
82
+ }
83
+ #expandDeclaration(name, visiting, namespace) {
84
+ const declaration = this.scope.normalized.get(name);
85
+ if (declaration === undefined)
86
+ return undefined;
87
+ if ('match' in declaration)
88
+ return branchesOf(declaration.match, namespace);
89
+ const nested = new Set(visiting);
90
+ nested.add(name);
91
+ const refs = 'allOf' in declaration ? declaration.allOf : declaration.anyOf;
92
+ const children = [];
93
+ for (const [index, ref] of refs.entries()) {
94
+ if (!this.scope.isLocalPolicy(ref))
95
+ return undefined;
96
+ const expanded = this.#expandedBranches(ref.name, nested, `${namespace}/${index}:${ref.name}`);
97
+ if (expanded === undefined)
98
+ return undefined;
99
+ children.push(expanded);
100
+ }
101
+ return 'allOf' in declaration ? branchProduct(children) : capBranches(children.flat());
102
+ }
103
+ #constraintsAreSatisfiable(branch, variants, use) {
104
+ const terms = new Set(use.kind === 'object' ? ['subject', 'object'] : ['subject', 'source', 'target']);
105
+ for (const key of branch.variables.keys())
106
+ terms.add(key);
107
+ for (const key of branch.terms.keys())
108
+ terms.add(key);
109
+ for (const edge of branch.edges) {
110
+ terms.add(termKey(edge.pattern.source, edge.namespace));
111
+ terms.add(termKey(edge.pattern.target, edge.namespace));
112
+ }
113
+ const union = new UnionFind(terms);
114
+ for (const [left, right] of branch.equalities)
115
+ union.join(left, right);
116
+ for (const variant of variants) {
117
+ for (const [left, right] of variant.equalities)
118
+ union.join(left, right);
119
+ }
120
+ const constraints = new Map();
121
+ for (const variant of variants) {
122
+ for (const [term, accepts] of variant.constraints) {
123
+ const root = union.find(term);
124
+ const values = constraints.get(root) ?? [];
125
+ values.push(accepts);
126
+ constraints.set(root, values);
127
+ }
128
+ }
129
+ const groups = new Map();
130
+ for (const term of terms) {
131
+ const root = union.find(term);
132
+ const members = groups.get(root) ?? [];
133
+ members.push(term);
134
+ groups.set(root, members);
135
+ }
136
+ for (const [root, members] of groups) {
137
+ const endpointConstraints = constraints.get(root) ?? [];
138
+ let candidates = [{ kind: 'open', requirements: [] }];
139
+ for (const member of members) {
140
+ candidates = this.#applyInitial(candidates, member, branch, use);
141
+ if (candidates.length === 0)
142
+ return false;
143
+ }
144
+ for (const endpoint of endpointConstraints) {
145
+ candidates = this.#applyEndpoint(candidates, endpoint);
146
+ if (candidates.length === 0)
147
+ return false;
148
+ }
149
+ }
150
+ return true;
151
+ }
152
+ #applyInitial(candidates, term, branch, use) {
153
+ if (term === 'subject')
154
+ return this.#applyAnyNodeClass(candidates);
155
+ const contract = targetContract(term, use);
156
+ if (contract !== undefined) {
157
+ if (contract.kind === 'exact')
158
+ return this.#applyExact(candidates, contract.ref);
159
+ if (contract.kind === 'default') {
160
+ return uniqueCandidates(this.scope.allNodeClasses.flatMap((ref) => this.#applyExact(candidates, ref)));
161
+ }
162
+ return candidates;
163
+ }
164
+ const referenced = branch.terms.get(term);
165
+ if (referenced?.kind === 'ref') {
166
+ const contract = this.scope.referenceContract(referenced.ref);
167
+ return contract?.kind === 'exact' ? this.#applyExact(candidates, contract.ref) : [];
168
+ }
169
+ const selector = branch.variables.get(term);
170
+ if (selector === undefined)
171
+ return [];
172
+ if (selector.kind === 'exact')
173
+ return this.#applyExact(candidates, selector.class);
174
+ if (selector.kind === 'any')
175
+ return candidates;
176
+ return this.#applyRequirement(candidates, selector.class);
177
+ }
178
+ #applyExact(candidates, exact) {
179
+ const result = [];
180
+ for (const candidate of candidates) {
181
+ if (candidate.kind === 'exact') {
182
+ if (definitionRefIndex(candidate.ref) === definitionRefIndex(exact))
183
+ result.push(candidate);
184
+ }
185
+ else if (candidate.requirements.every((required) => this.scope.classExtends(exact, required))) {
186
+ result.push({ kind: 'exact', ref: exact });
187
+ }
188
+ }
189
+ return uniqueCandidates(result);
190
+ }
191
+ #applyAnyNodeClass(candidates) {
192
+ return uniqueCandidates(candidates.flatMap((candidate) => candidate.kind === 'exact'
193
+ ? [candidate]
194
+ : this.scope.allNodeClasses
195
+ .filter((ref) => candidate.requirements.every((required) => this.scope.classExtends(ref, required)))
196
+ .map((ref) => ({ kind: 'exact', ref }))));
197
+ }
198
+ #applyRequirement(candidates, required) {
199
+ const result = [];
200
+ for (const candidate of candidates) {
201
+ if (candidate.kind === 'exact') {
202
+ if (this.scope.classExtends(candidate.ref, required))
203
+ result.push(candidate);
204
+ }
205
+ else {
206
+ result.push({
207
+ kind: 'open',
208
+ requirements: uniqueClasses([...candidate.requirements, required]),
209
+ });
210
+ }
211
+ }
212
+ return uniqueCandidates(result);
213
+ }
214
+ #applyEndpoint(candidates, accepts) {
215
+ const result = [];
216
+ for (const candidate of candidates) {
217
+ if (candidate.kind === 'exact') {
218
+ if (this.#classSatisfiesEndpoint(candidate.ref, accepts))
219
+ result.push(candidate);
220
+ continue;
221
+ }
222
+ for (const accept of accepts) {
223
+ result.push({
224
+ kind: 'open',
225
+ requirements: uniqueClasses([...candidate.requirements, accept]),
226
+ });
227
+ }
228
+ }
229
+ return uniqueCandidates(result);
230
+ }
231
+ #classSatisfiesEndpoint(candidate, accepts) {
232
+ return accepts.some((accept) => this.scope.classExtends(candidate, accept));
233
+ }
234
+ }
235
+ function targetContract(term, use) {
236
+ if (use.kind === 'object')
237
+ return term === 'object' ? use.object : undefined;
238
+ if (term === 'source')
239
+ return use.source;
240
+ if (term === 'target')
241
+ return use.target;
242
+ return undefined;
243
+ }
244
+ function edgeAlternatives(edges, typing) {
245
+ let result = [[]];
246
+ for (const edge of edges) {
247
+ const variants = variantsForEdge(edge, typing);
248
+ if (variants.length === 0)
249
+ return [];
250
+ const next = [];
251
+ for (const prefix of result) {
252
+ for (const variant of variants)
253
+ next.push([...prefix, variant]);
254
+ }
255
+ result = next;
256
+ }
257
+ return result;
258
+ }
259
+ function variantsForEdge(use, typing) {
260
+ const edge = use.pattern;
261
+ const declaration = typing.scope.edgeDeclaration(edge.class);
262
+ if (declaration === undefined)
263
+ return [];
264
+ const endpoints = declaration.orientation === 'directed'
265
+ ? [declaration.endpoints.source, declaration.endpoints.target]
266
+ : declaration.endpoints;
267
+ const bounds = edge.repeat ?? { min: 1, max: 1 };
268
+ if (!Number.isInteger(bounds.min) ||
269
+ !Number.isInteger(bounds.max) ||
270
+ bounds.min < 0 ||
271
+ bounds.max < bounds.min ||
272
+ bounds.max > MAX_REPEAT) {
273
+ return [];
274
+ }
275
+ const sourceKey = termKey(edge.source, use.namespace);
276
+ const targetKey = termKey(edge.target, use.namespace);
277
+ const variants = [];
278
+ for (let length = bounds.min; length <= bounds.max; length++) {
279
+ if (length === 0) {
280
+ variants.push({ constraints: [], equalities: [[sourceKey, targetKey]] });
281
+ continue;
282
+ }
283
+ const orientationCount = declaration.orientation === 'undirected' ? 2 ** length : 1;
284
+ for (let mask = 0; mask < orientationCount; mask++) {
285
+ const orientations = Array.from({ length }, (_, index) => declaration.orientation === 'undirected' && (mask & (1 << index)) !== 0);
286
+ let transitionsValid = true;
287
+ for (let index = 0; index + 1 < length; index++) {
288
+ const incoming = endEndpoint(endpoints, orientations[index] ?? false);
289
+ const outgoing = startEndpoint(endpoints, orientations[index + 1] ?? false);
290
+ if (!typing.endpointsOverlap(incoming.accepts, outgoing.accepts)) {
291
+ transitionsValid = false;
292
+ break;
293
+ }
294
+ }
295
+ if (!transitionsValid)
296
+ continue;
297
+ const first = startEndpoint(endpoints, orientations[0] ?? false);
298
+ const last = endEndpoint(endpoints, orientations[length - 1] ?? false);
299
+ variants.push({
300
+ constraints: [
301
+ [sourceKey, first.accepts],
302
+ [targetKey, last.accepts],
303
+ ],
304
+ equalities: [],
305
+ });
306
+ }
307
+ }
308
+ return uniqueEdgeVariants(variants);
309
+ }
310
+ function startEndpoint(endpoints, reversed) {
311
+ return reversed ? endpoints[1] : endpoints[0];
312
+ }
313
+ function endEndpoint(endpoints, reversed) {
314
+ return reversed ? endpoints[0] : endpoints[1];
315
+ }
316
+ function uniqueEdgeVariants(values) {
317
+ const result = new Map();
318
+ for (const value of values) {
319
+ const key = `${value.constraints
320
+ .map(([term, accepts]) => `${term}:${accepts.map(definitionRefIndex).sort().join(',')}`)
321
+ .join('|')}=${value.equalities.map(([left, right]) => `${left}:${right}`).join('|')}`;
322
+ result.set(key, value);
323
+ }
324
+ return [...result.values()];
325
+ }
326
+ function uniqueCandidates(values) {
327
+ const result = new Map();
328
+ for (const value of values) {
329
+ const key = value.kind === 'exact'
330
+ ? `exact:${definitionRefIndex(value.ref)}`
331
+ : `open:${value.requirements.map(definitionRefIndex).sort().join('|')}`;
332
+ result.set(key, value);
333
+ }
334
+ return [...result.values()];
335
+ }
336
+ function uniqueClasses(values) {
337
+ return [...new Map(values.map((value) => [definitionRefIndex(value), value])).values()];
338
+ }
@@ -1,4 +1,4 @@
1
- export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | 'DOMAIN_VALIDATION_TARGET_UNKNOWN' | 'VALUE_SCHEMA_INSTANCE_INVALID' | 'VS_DIALECT' | 'VS_DYNAMIC_FORBIDDEN' | 'VS_KEYWORD_CLOSED' | 'VS_META_SCHEMA' | 'VS_NUMBER_DOMAIN' | 'VS_PATH_EXTENSION' | 'VS_RECURSION_PRODUCTIVE' | 'VS_REFERENCE_FORM' | 'VS_REFERENCE_RESOLUTION' | 'VS_REGEX_PORTABLE' | 'VS_RESOURCE_ID' | 'VS_VALUE_KIND';
1
+ export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | 'DOMAIN_VALIDATION_TARGET_UNKNOWN' | 'VALUE_SCHEMA_INSTANCE_INVALID' | 'VS_DIALECT' | 'VS_DYNAMIC_FORBIDDEN' | 'VS_KEYWORD_CLOSED' | 'VS_META_SCHEMA' | 'VS_NUMBER_DOMAIN' | 'VS_PATH_EXTENSION' | 'VS_RECURSION_PRODUCTIVE' | 'VS_REFERENCE_FORM' | 'VS_REFERENCE_RESOLUTION' | 'VS_REGEX_PORTABLE' | 'VS_RESOURCE_ID' | 'VS_VALUE_KIND' | 'VS_VALUE_KIND_JSON';
2
2
  export interface Diagnostic<out Code extends DiagnosticCode = DiagnosticCode> {
3
3
  readonly code: Code;
4
4
  readonly pointer: string;
@@ -6,3 +6,17 @@ export interface Diagnostic<out Code extends DiagnosticCode = DiagnosticCode> {
6
6
  readonly ref?: string;
7
7
  }
8
8
  export declare function diagnostic<const Code extends DiagnosticCode>(code: Code, pointer: string, message: string, ref?: string): Diagnostic<Code>;
9
+ /** The fields every diagnostic family shares. */
10
+ interface DiagnosticFields {
11
+ readonly code: string;
12
+ readonly pointer: string;
13
+ readonly message: string;
14
+ readonly ref?: string;
15
+ }
16
+ /** Order diagnostics by pointer, then code, then message. */
17
+ export declare function orderDiagnostics<Value extends DiagnosticFields>(diagnostics: readonly Value[]): Value[];
18
+ /** Keep one of each pointer, code, ref and message, then order them. */
19
+ export declare function orderAndDeduplicate<Value extends DiagnosticFields>(diagnostics: readonly Value[]): Value[];
20
+ /** One line per diagnostic: its pointer (`/` for the root), its code and its message. */
21
+ export declare function formatDiagnostics(diagnostics: readonly DiagnosticFields[]): string;
22
+ export {};
@@ -1,3 +1,24 @@
1
+ import { compareUnicode } from '../../addressing/ordering.js';
1
2
  export function diagnostic(code, pointer, message, ref) {
2
3
  return Object.freeze({ code, pointer, message, ...(ref === undefined ? {} : { ref }) });
3
4
  }
5
+ /** Order diagnostics by pointer, then code, then message. */
6
+ export function orderDiagnostics(diagnostics) {
7
+ return [...diagnostics].sort((left, right) => compareUnicode(left.pointer, right.pointer) ||
8
+ compareUnicode(left.code, right.code) ||
9
+ compareUnicode(left.message, right.message));
10
+ }
11
+ /** Keep one of each pointer, code, ref and message, then order them. */
12
+ export function orderAndDeduplicate(diagnostics) {
13
+ const unique = new Map();
14
+ for (const value of diagnostics) {
15
+ unique.set(`${value.pointer}\u0000${value.code}\u0000${value.ref ?? ''}\u0000${value.message}`, value);
16
+ }
17
+ return orderDiagnostics([...unique.values()]);
18
+ }
19
+ /** One line per diagnostic: its pointer (`/` for the root), its code and its message. */
20
+ export function formatDiagnostics(diagnostics) {
21
+ return diagnostics
22
+ .map(({ code, pointer, message }) => `${pointer || '/'} [${code}] ${message}`)
23
+ .join('\n');
24
+ }
@@ -1,3 +1,5 @@
1
+ import { compareCodeUnits } from '../../../addressing/ordering.js';
2
+ import { escapePointerToken } from '../syntax/pointer.js';
1
3
  /** Exact field constraints available for independent scalar route validation. */
2
4
  export function inputFieldNodes(program) {
3
5
  const fields = new Map();
@@ -34,7 +36,7 @@ export function inputFieldNodes(program) {
34
36
  Array.isArray(schema.properties))
35
37
  return;
36
38
  for (const name of Object.keys(schema.properties)) {
37
- const child = node.children[`properties/${escapePointer(name)}`];
39
+ const child = node.children[`properties/${escapePointerToken(name)}`];
38
40
  if (child === undefined)
39
41
  throw new TypeError(`Compiled input field ${name} is absent.`);
40
42
  const constraints = fields.get(name) ?? new Set();
@@ -46,15 +48,9 @@ export function inputFieldNodes(program) {
46
48
  if (!closed || unsupported)
47
49
  return undefined;
48
50
  return Object.freeze(Object.fromEntries([...fields]
49
- .sort(([left], [right]) => compare(left, right))
51
+ .sort(([left], [right]) => compareCodeUnits(left, right))
50
52
  .map(([name, nodes]) => [
51
53
  name,
52
54
  Object.freeze([...nodes].sort((left, right) => left - right)),
53
55
  ])));
54
56
  }
55
- function escapePointer(value) {
56
- return value.replaceAll('~', '~0').replaceAll('/', '~1');
57
- }
58
- function compare(left, right) {
59
- return left < right ? -1 : left > right ? 1 : 0;
60
- }
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for the array size, uniqueness, item and `contains` keywords. */
3
+ export declare const arrayRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,85 @@
1
+ import { has } from './evidence.js';
2
+ import { isTop } from './keywords.js';
3
+ import { ARRAY } from './kinds.js';
4
+ import { childOf, childrenOf, schemaOf } from './location.js';
5
+ /** Rules for the array size, uniqueness, item and `contains` keywords. */
6
+ export const arrayRules = new Map([
7
+ [
8
+ 'maxItems',
9
+ ({ value, evidence }) => typeof value !== 'number' ||
10
+ !has(evidence, ARRAY) ||
11
+ (evidence.maxItems !== undefined && evidence.maxItems <= value),
12
+ ],
13
+ [
14
+ 'minItems',
15
+ ({ value, evidence }) => typeof value !== 'number' ||
16
+ !has(evidence, ARRAY) ||
17
+ value <= 0 ||
18
+ evidence.minItems >= value,
19
+ ],
20
+ [
21
+ 'uniqueItems',
22
+ ({ value, evidence }) => value !== true ||
23
+ !has(evidence, ARRAY) ||
24
+ evidence.uniqueItems ||
25
+ (evidence.maxItems !== undefined && evidence.maxItems <= 1),
26
+ ],
27
+ ['prefixItems', itemsHold],
28
+ ['items', itemsHold],
29
+ ['contains', containsHolds],
30
+ ['minContains', containsHolds],
31
+ ['maxContains', containsHolds],
32
+ ]);
33
+ function itemsHold({ inclusion, outer, schema, evidence }) {
34
+ if (!has(evidence, ARRAY))
35
+ return true;
36
+ const prefix = childrenOf(outer, 'prefixItems');
37
+ const rest = Object.hasOwn(schema, 'items') ? childOf(outer, 'items') : undefined;
38
+ const width = Math.max(prefix.length, ...evidence.conjuncts.map((conjunct) => childrenOf(conjunct, 'prefixItems').length));
39
+ // Every index at or beyond `width` meets `items` on both sides.
40
+ for (let index = 0; index <= width; index++) {
41
+ const required = index < prefix.length ? prefix[index] : rest;
42
+ if (required === undefined || isTop(schemaOf(required)))
43
+ continue;
44
+ if (evidence.maxItems !== undefined && evidence.maxItems <= index)
45
+ continue;
46
+ const admitted = evidence.conjuncts.flatMap((conjunct) => {
47
+ const own = childrenOf(conjunct, 'prefixItems');
48
+ if (index < own.length)
49
+ return [own[index]];
50
+ const items = childOf(conjunct, 'items');
51
+ return items === undefined ? [] : [items];
52
+ });
53
+ if (!admitted.some((constraint) => inclusion.includes(required, constraint)))
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+ function containsHolds({ inclusion, outer, schema, evidence }) {
59
+ const contains = childOf(outer, 'contains');
60
+ if (contains === undefined || !has(evidence, ARRAY))
61
+ return true;
62
+ const { minimum, maximum } = containsCounts(schema);
63
+ const ownContains = evidence.conjuncts.flatMap((conjunct) => {
64
+ const own = childOf(conjunct, 'contains');
65
+ if (own === undefined)
66
+ return [];
67
+ const conjunctSchema = schemaOf(conjunct);
68
+ return [{ at: own, ...containsCounts(conjunctSchema) }];
69
+ });
70
+ // Every inner match is an outer match, so outer counts at least as many.
71
+ const atLeast = minimum <= 0 ||
72
+ ownContains.some((own) => own.minimum >= minimum && inclusion.includes(contains, own.at));
73
+ // Every outer match is an inner match, so outer counts at most as many.
74
+ const atMost = maximum === Number.POSITIVE_INFINITY ||
75
+ (evidence.maxItems !== undefined && evidence.maxItems <= maximum) ||
76
+ ownContains.some((own) => own.maximum <= maximum && inclusion.includes(own.at, contains));
77
+ return atLeast && atMost;
78
+ }
79
+ /** The match counts `contains` requires, with the evaluator's defaults. */
80
+ function containsCounts(schema) {
81
+ return {
82
+ minimum: typeof schema.minContains === 'number' ? schema.minContains : 1,
83
+ maximum: typeof schema.maxContains === 'number' ? schema.maxContains : Number.POSITIVE_INFINITY,
84
+ };
85
+ }
@@ -0,0 +1,18 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { Evidence } from './evidence.js';
3
+ import type { Inclusion } from './includes.js';
4
+ import type { At } from './location.js';
5
+ /** One keyword of `outer`, to be proved from the evidence of `inner`. */
6
+ export interface Assertion {
7
+ readonly inclusion: Inclusion;
8
+ readonly keyword: string;
9
+ readonly value: JsonValue | undefined;
10
+ readonly outer: At;
11
+ readonly schema: Readonly<Record<string, JsonValue>>;
12
+ readonly inner: At;
13
+ readonly evidence: Evidence;
14
+ }
15
+ /** Whether one outer keyword holds for every value the inner evidence admits. */
16
+ export type Rule = (assertion: Assertion) => boolean;
17
+ /** Whether every keyword of the outer `schema` holds for the values `inner` admits. */
18
+ export declare function assertionsHold(inclusion: Inclusion, outer: At, schema: Readonly<Record<string, JsonValue>>, inner: At, evidence: Evidence): boolean;
@@ -0,0 +1,29 @@
1
+ import { arrayRules } from './arrays.js';
2
+ import { combinatorRules } from './combinators.js';
3
+ import { INERT } from './keywords.js';
4
+ import { numberRules } from './numbers.js';
5
+ import { objectRules } from './objects.js';
6
+ import { stringRules } from './strings.js';
7
+ import { typeRules } from './types.js';
8
+ const RULES = new Map([
9
+ ...combinatorRules,
10
+ ...typeRules,
11
+ ...numberRules,
12
+ ...stringRules,
13
+ ...arrayRules,
14
+ ...objectRules,
15
+ ]);
16
+ /** Whether every keyword of the outer `schema` holds for the values `inner` admits. */
17
+ export function assertionsHold(inclusion, outer, schema, inner, evidence) {
18
+ // Annotation-dependent keywords are decided only by equivalence.
19
+ if (Object.hasOwn(schema, 'unevaluatedProperties'))
20
+ return false;
21
+ if (Object.hasOwn(schema, 'unevaluatedItems'))
22
+ return false;
23
+ return Object.keys(schema).every((keyword) => {
24
+ const rule = RULES.get(keyword);
25
+ if (rule === undefined)
26
+ return INERT.has(keyword);
27
+ return rule({ inclusion, keyword, value: schema[keyword], outer, schema, inner, evidence });
28
+ });
29
+ }
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for `$ref`, the boolean combinators and the conditional keywords. */
3
+ export declare const combinatorRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,67 @@
1
+ import { equivalent } from './equivalence.js';
2
+ import { childOf, childrenOf, referenceOf } from './location.js';
3
+ /** Rules for `$ref`, the boolean combinators and the conditional keywords. */
4
+ export const combinatorRules = new Map([
5
+ [
6
+ '$ref',
7
+ ({ inclusion, outer, inner }) => {
8
+ const target = referenceOf(outer);
9
+ return target !== undefined && inclusion.includes(target, inner);
10
+ },
11
+ ],
12
+ [
13
+ 'allOf',
14
+ ({ inclusion, outer, inner }) => childrenOf(outer, 'allOf').every((branch) => inclusion.includes(branch, inner)),
15
+ ],
16
+ [
17
+ 'anyOf',
18
+ ({ inclusion, outer, inner }) => childrenOf(outer, 'anyOf').some((branch) => inclusion.includes(branch, inner)),
19
+ ],
20
+ ['oneOf', () => false],
21
+ [
22
+ 'not',
23
+ ({ inclusion, outer, evidence }) => {
24
+ const negated = childOf(outer, 'not');
25
+ return negated !== undefined && excludes(inclusion, negated, evidence);
26
+ },
27
+ ],
28
+ ['if', conditionalHolds],
29
+ ['then', conditionalHolds],
30
+ ['else', conditionalHolds],
31
+ ]);
32
+ /** Whether no value admitted by `evidence` satisfies `negated`. */
33
+ function excludes(inclusion, negated, evidence) {
34
+ if ((inclusion.evidence(negated).kinds & evidence.kinds) === 0)
35
+ return true;
36
+ return evidence.conjuncts.some((conjunct) => {
37
+ const own = childOf(conjunct, 'not');
38
+ return own !== undefined && inclusion.includes(own, negated);
39
+ });
40
+ }
41
+ function conditionalHolds({ inclusion, outer, inner, evidence }) {
42
+ const condition = childOf(outer, 'if');
43
+ if (condition === undefined)
44
+ return true;
45
+ const then = childOf(outer, 'then');
46
+ const otherwise = childOf(outer, 'else');
47
+ const thenHolds = (fallback) => then === undefined ||
48
+ inclusion.includes(then, inner) ||
49
+ (fallback !== undefined && inclusion.includes(then, fallback));
50
+ const elseHolds = (fallback) => otherwise === undefined ||
51
+ inclusion.includes(otherwise, inner) ||
52
+ (fallback !== undefined && inclusion.includes(otherwise, fallback));
53
+ if (thenHolds() && elseHolds())
54
+ return true;
55
+ for (const conjunct of evidence.conjuncts) {
56
+ const own = childOf(conjunct, 'if');
57
+ if (own === undefined || !equivalent(inclusion, own, condition))
58
+ continue;
59
+ if (thenHolds(childOf(conjunct, 'then')) && elseHolds(childOf(conjunct, 'else')))
60
+ return true;
61
+ }
62
+ if (inclusion.includes(condition, inner))
63
+ return thenHolds();
64
+ if (excludes(inclusion, condition, evidence))
65
+ return elseHolds();
66
+ return false;
67
+ }
@@ -0,0 +1,4 @@
1
+ import type { Inclusion } from './includes.js';
2
+ import type { At } from './location.js';
3
+ /** Whether the two locations are bisimilar: equal assertions over paired subschemas. */
4
+ export declare function equivalent(inclusion: Inclusion, left: At, right: At): boolean;