@astrale-os/kernel-dsl 1.0.0-beta.15 → 1.0.0-beta.17
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.
- package/dist/v1/builder/class/authoring.js +3 -2
- package/dist/v1/builder/class/input.d.ts +45 -14
- package/dist/v1/builder/index.d.ts +2 -1
- package/dist/v1/builder/lowering/class.d.ts +8 -0
- package/dist/v1/builder/lowering/class.js +40 -16
- package/dist/v1/builder/lowering/schema/compile.js +3 -3
- package/dist/v1/builder/policy/authoring.js +97 -88
- package/dist/v1/bundle/model.js +1 -1
- package/dist/v1/compiled/admission/accept.js +1 -7
- package/dist/v1/compiled/admission/semantics/class.js +2 -3
- package/dist/v1/compiled/admission/semantics/policy/parity.js +25 -13
- package/dist/v1/compiled/admission/semantics/policy/typing.js +2 -2
- package/dist/v1/compiled/admission/wire/class.js +23 -18
- package/dist/v1/compiled/admission/wire/policy.d.ts +4 -3
- package/dist/v1/compiled/admission/wire/policy.js +8 -7
- package/dist/v1/compiled/loader/load.js +1 -1
- package/dist/v1/domain/create.js +1 -1
- package/dist/v1/domain/model/input.d.ts +2 -1
- package/dist/v1/domain/projection/classes.js +1 -1
- package/dist/v1/domain/validation/create.d.ts +2 -1
- package/dist/v1/domain/validation/create.js +1 -1
- package/dist/v1/index.d.ts +1 -1
- package/dist/v1/language/.spec/schemas/draft-2020-12.schema.json +462 -0
- package/dist/v1/language/callable/resolved.d.ts +11 -2
- package/dist/v1/language/class/model/class.d.ts +17 -5
- package/dist/v1/language/freeze.d.ts +5 -0
- package/dist/v1/{schema/canonical → language}/freeze.js +4 -0
- package/dist/v1/language/policy/analysis/typing.d.ts +5 -2
- package/dist/v1/language/policy/analysis/typing.js +158 -42
- package/dist/v1/language/policy/model/policy.d.ts +44 -4
- package/dist/v1/language/policy/model/policy.js +14 -0
- package/dist/v1/language/policy/normalization/normalize.d.ts +6 -6
- package/dist/v1/language/policy/normalization/normalize.js +5 -5
- package/dist/v1/language/validation/rule.d.ts +22 -6
- package/dist/v1/language/validation/rule.js +4 -15
- package/dist/v1/language/value/evaluate/program.d.ts +2 -1
- package/dist/v1/language/value/evaluate/program.js +2 -2
- package/dist/v1/language/value/evaluate/validate.d.ts +2 -2
- package/dist/v1/language/value/evaluate/validate.js +2 -2
- package/dist/v1/language/value/ordering.d.ts +5 -0
- package/dist/v1/language/value/ordering.js +14 -0
- package/dist/v1/language/value/profile/json.validate.d.ts +3 -1
- package/dist/v1/language/value/profile/json.validate.js +3 -2
- package/dist/v1/language/value/reference/closure.js +3 -4
- package/dist/v1/language/value/reference/registry.index.d.ts +2 -0
- package/dist/v1/language/value/reference/registry.index.js +31 -15
- package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +210 -27
- package/dist/v1/schema/admission/callable.js +1 -1
- package/dist/v1/schema/canonical/revision.js +4 -4
- package/dist/v1/schema/compatibility/comparators/callables.js +1 -1
- package/dist/v1/schema/compatibility/comparators/classes.js +4 -4
- package/dist/v1/schema/compatibility/comparators/members.js +1 -1
- package/dist/v1/schema/compatibility/comparators/values.js +2 -2
- package/dist/v1/schema/compatibility/report.js +1 -1
- package/dist/v1/schema/compatibility/versioning/index.js +1 -1
- package/dist/v1/schema/model/schema.js +1 -1
- package/dist/v1/schema/normalization/domain.js +9 -8
- package/dist/v1/schema/resolution/domain.js +1 -1
- package/dist/v1/schema/resolution/origins.js +3 -2
- package/dist/v1/schema/rule.d.ts +8 -14
- package/dist/v1/schema/rule.js +14 -2
- package/dist/v1/schema/validation/class.d.ts +6 -2
- package/dist/v1/schema/validation/class.js +20 -25
- package/dist/v1/schema/validation/domain.js +1 -1
- package/dist/v1/schema/validation/domain.json-schema.d.ts +2 -2
- package/dist/v1/schema/validation/domain.json-schema.js +20 -6
- package/dist/v1/schema/validation/domain.structure.d.ts +4765 -1005
- package/dist/v1/schema/validation/domain.structure.js +21 -9
- package/dist/v1/schema/validation/json.json-schema.d.ts +18 -2
- package/dist/v1/schema/validation/json.json-schema.js +14 -4
- package/dist/v1/schema/validation/policy/budgets.d.ts +1 -1
- package/dist/v1/schema/validation/policy/budgets.js +4 -4
- package/dist/v1/schema/validation/policy/context.d.ts +1 -2
- package/dist/v1/schema/validation/policy/context.js +2 -7
- package/dist/v1/schema/validation/policy/uses.d.ts +10 -2
- package/dist/v1/schema/validation/policy/uses.js +26 -13
- package/dist/v1/schema/validation/policy/validate.js +2 -3
- package/dist/v1/schema/validation/value-api.d.ts +2 -0
- package/dist/v1/schema/validation/value-api.js +2 -0
- package/dist/v1/schema/validation/value.json-schema.d.ts +7 -9
- package/dist/v1/schema/validation/value.json-schema.js +38 -27
- package/package.json +2 -1
- package/dist/v1/schema/canonical/domain.d.ts +0 -3
- package/dist/v1/schema/canonical/domain.digest.d.ts +0 -3
- package/dist/v1/schema/canonical/domain.digest.js +0 -5
- package/dist/v1/schema/canonical/domain.js +0 -5
- package/dist/v1/schema/canonical/freeze.d.ts +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Options } from '@astrale-os/kernel-dsl/value';
|
|
1
2
|
import type { DefinitionKey, FunctionRef, Key, MethodKey } from '../../addressing/index.js';
|
|
2
3
|
import type { ResolvedClass } from '../class/resolved.js';
|
|
3
4
|
import type { CallablePolicy } from '../policy/model/policy.js';
|
|
@@ -36,7 +37,11 @@ export interface ResolvedMethod<out Input = unknown, out Output = unknown, out S
|
|
|
36
37
|
readonly static: Static;
|
|
37
38
|
readonly abstract: Abstract;
|
|
38
39
|
};
|
|
39
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The issues of `input` against the callable's input schema, `options` overriding the portable
|
|
42
|
+
* limits it is admitted under: those it crossed the wire under, for a call's input.
|
|
43
|
+
*/
|
|
44
|
+
validateInput(input: unknown, options?: Options): readonly Diagnostic[];
|
|
40
45
|
validateInputField(field: string, input: unknown): readonly Diagnostic[];
|
|
41
46
|
validateOutput(output: unknown): readonly Diagnostic[];
|
|
42
47
|
readonly __input?: Input;
|
|
@@ -62,7 +67,11 @@ export interface ResolvedFunction<out Input = unknown, out Output = unknown, out
|
|
|
62
67
|
/** Present only when the callable input is a safely enumerable closed object. */
|
|
63
68
|
readonly inputFields?: readonly string[];
|
|
64
69
|
readonly definition: CallableDefinitionOf<FunctionDefinition, Auth, Policy>;
|
|
65
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The issues of `input` against the callable's input schema, `options` overriding the portable
|
|
72
|
+
* limits it is admitted under: those it crossed the wire under, for a call's input.
|
|
73
|
+
*/
|
|
74
|
+
validateInput(input: unknown, options?: Options): readonly Diagnostic[];
|
|
66
75
|
validateInputField(field: string, input: unknown): readonly Diagnostic[];
|
|
67
76
|
validateOutput(output: unknown): readonly Diagnostic[];
|
|
68
77
|
readonly __input?: Input;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClassRef, DefinitionBase, PropertyKey } from '../../../addressing/index.js';
|
|
2
2
|
import type { MethodRecord } from '../../callable/model/callable.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { EdgeClassPolicy, EdgeClassWritePolicy, NodeClassCreatePolicy, NodeClassPolicy, NodeClassWritePolicy } from '../../policy/model/policy.js';
|
|
4
4
|
import type { DataFacet } from './data.js';
|
|
5
5
|
import type { EdgeContract } from './edge.js';
|
|
6
6
|
import type { EdgePropertyDefinition, NodePropertyDefinition, PropertyFacet } from './property.js';
|
|
@@ -10,15 +10,27 @@ export type ClassKind = 'node' | 'edge';
|
|
|
10
10
|
* on that Domain; `'internal'`, those of its Domain only; `'final'`, none.
|
|
11
11
|
*/
|
|
12
12
|
export type Inheritance = 'open' | 'internal' | 'final';
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* An absent observation rule admits every caller; a delegation is still scoped by its ceiling. An
|
|
15
|
+
* absent write rule admits only the Domain that declares the Class.
|
|
16
|
+
*/
|
|
14
17
|
export interface NodeClassPolicies {
|
|
15
18
|
readonly read?: NodeClassPolicy;
|
|
16
19
|
readonly traverse?: NodeClassPolicy;
|
|
20
|
+
readonly create?: NodeClassCreatePolicy;
|
|
21
|
+
readonly update?: NodeClassWritePolicy;
|
|
22
|
+
readonly delete?: NodeClassWritePolicy;
|
|
17
23
|
}
|
|
18
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* An absent observation rule admits every caller; a delegation is still scoped by its ceiling. An
|
|
26
|
+
* absent write rule admits only the Domain that declares the Class.
|
|
27
|
+
*/
|
|
19
28
|
export interface EdgeClassPolicies {
|
|
20
|
-
readonly read?:
|
|
21
|
-
readonly traverse?:
|
|
29
|
+
readonly read?: EdgeClassPolicy;
|
|
30
|
+
readonly traverse?: EdgeClassPolicy;
|
|
31
|
+
readonly create?: EdgeClassWritePolicy;
|
|
32
|
+
readonly update?: EdgeClassWritePolicy;
|
|
33
|
+
readonly delete?: EdgeClassWritePolicy;
|
|
22
34
|
}
|
|
23
35
|
/**
|
|
24
36
|
* The Properties one key of a Node Class names, own or inherited, which no two Nodes of the Classes
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freeze `value` and every object it reaches, in place. An object already frozen is taken as frozen
|
|
3
|
+
* through, so a value built from frozen parts is frozen without walking them again.
|
|
4
|
+
*/
|
|
1
5
|
export function deepFreeze(value) {
|
|
2
6
|
if (value === null || typeof value !== 'object' || Object.isFrozen(value)) {
|
|
3
7
|
return value;
|
|
@@ -2,6 +2,7 @@ import type { ClassRef, PolicyRef } from '../../../addressing/index.js';
|
|
|
2
2
|
import type { EdgeContract } from '../../class/model/edge.js';
|
|
3
3
|
import type { PolicyExpression, PolicyObjectRef } from '../model/policy.js';
|
|
4
4
|
import type { CheckLeafClasses } from './checks.js';
|
|
5
|
+
import { type ClassAncestry } from '../../class/inheritance.js';
|
|
5
6
|
import { type PatternBranch } from './branches.js';
|
|
6
7
|
export type ObjectContract = {
|
|
7
8
|
readonly kind: 'default';
|
|
@@ -20,11 +21,13 @@ export type PolicyUseContract = {
|
|
|
20
21
|
/** What Policy typing reads of the closure it types against: source or compiled admission. */
|
|
21
22
|
export interface PolicyTypingScope {
|
|
22
23
|
readonly normalized: ReadonlyMap<string, PolicyExpression>;
|
|
24
|
+
/** Each resolves, through `resolveClass`, to a Node Class. */
|
|
23
25
|
readonly allNodeClasses: readonly ClassRef[];
|
|
26
|
+
/** The concrete Classes of `allNodeClasses`, in its order. */
|
|
24
27
|
readonly concreteNodeClasses: readonly ClassRef[];
|
|
25
28
|
isLocalPolicy(ref: PolicyRef): boolean;
|
|
26
|
-
/**
|
|
27
|
-
|
|
29
|
+
/** The Class a ref names in the closure, as satisfaction reads it. */
|
|
30
|
+
resolveClass(ref: ClassRef): ClassAncestry | undefined;
|
|
28
31
|
edgeDeclaration(ref: ClassRef): EdgeContract | undefined;
|
|
29
32
|
/** The contract of a Node a Policy pattern references, `undefined` when it has none. */
|
|
30
33
|
referenceContract(ref: PolicyObjectRef): ObjectContract | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Key } from '../../../addressing/index.js';
|
|
2
2
|
import { definitionRefIndex } from '../../../addressing/ordering.js';
|
|
3
|
+
import { classSatisfies, isGraphRoot } from '../../class/inheritance.js';
|
|
3
4
|
import { limits } from '../../limits.js';
|
|
4
5
|
import { branchesOf, branchProduct, capBranches, targetMode, termKey, UnionFind, } from './branches.js';
|
|
5
6
|
import { admittedCheckClasses } from './checks.js';
|
|
@@ -15,6 +16,7 @@ export class PolicyTyping {
|
|
|
15
16
|
// paths; for that to merge distinct variables, the Policy expands deeper than
|
|
16
17
|
// `limits.policy.patternDepth`.
|
|
17
18
|
#expansions = new Map();
|
|
19
|
+
#index;
|
|
18
20
|
constructor(scope) {
|
|
19
21
|
this.scope = scope;
|
|
20
22
|
}
|
|
@@ -27,9 +29,7 @@ export class PolicyTyping {
|
|
|
27
29
|
}
|
|
28
30
|
/** Whether the Policy accepts every concrete source and target pair the Edge Class accepts. */
|
|
29
31
|
policyAcceptsEdgeClass(name, declaration) {
|
|
30
|
-
const endpoints = declaration
|
|
31
|
-
? [declaration.endpoints.source, declaration.endpoints.target]
|
|
32
|
-
: declaration.endpoints;
|
|
32
|
+
const endpoints = endpointPair(declaration);
|
|
33
33
|
const sources = this.#concreteEndpointClasses(endpoints[0].accepts);
|
|
34
34
|
const targets = this.#concreteEndpointClasses(endpoints[1].accepts);
|
|
35
35
|
return sources.every((source) => targets.every((target) => this.#policyAcceptsEdge(name, { kind: 'exact', ref: source }, { kind: 'exact', ref: target })));
|
|
@@ -69,7 +69,12 @@ export class PolicyTyping {
|
|
|
69
69
|
return branches !== undefined && branches.every((branch) => this.branchIsTyped(branch, use));
|
|
70
70
|
}
|
|
71
71
|
#concreteEndpointClasses(accepts) {
|
|
72
|
-
|
|
72
|
+
const { concrete } = this.#nodeClassIndex();
|
|
73
|
+
return this.#satisfyingOneOf(accepts).filter((ref) => concrete.has(definitionRefIndex(ref)));
|
|
74
|
+
}
|
|
75
|
+
#nodeClassIndex() {
|
|
76
|
+
this.#index ??= nodeClassIndex(this.scope);
|
|
77
|
+
return this.#index;
|
|
73
78
|
}
|
|
74
79
|
#expandedBranches(name, visiting, namespace) {
|
|
75
80
|
if (visiting.has(name))
|
|
@@ -158,7 +163,7 @@ export class PolicyTyping {
|
|
|
158
163
|
if (contract.kind === 'exact')
|
|
159
164
|
return this.#applyExact(candidates, contract.ref);
|
|
160
165
|
if (contract.kind === 'default') {
|
|
161
|
-
return
|
|
166
|
+
return this.#applyAnyNodeClass(candidates).filter((candidate) => candidate.kind !== 'exact' || this.#isNodeClass(candidate.ref));
|
|
162
167
|
}
|
|
163
168
|
return candidates;
|
|
164
169
|
}
|
|
@@ -183,34 +188,38 @@ export class PolicyTyping {
|
|
|
183
188
|
if (definitionRefIndex(candidate.ref) === definitionRefIndex(exact))
|
|
184
189
|
result.push(candidate);
|
|
185
190
|
}
|
|
186
|
-
else if (candidate.
|
|
191
|
+
else if (candidate.kind === 'node') {
|
|
192
|
+
if (this.#isNodeClass(exact) && this.#satisfiesLists(exact, candidate.lists)) {
|
|
193
|
+
result.push({ kind: 'exact', ref: exact });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (candidate.requirements.every((required) => this.#classExtends(exact, required))) {
|
|
187
197
|
result.push({ kind: 'exact', ref: exact });
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
|
-
return
|
|
200
|
+
return uniqueBy(result, candidateKey);
|
|
191
201
|
}
|
|
192
202
|
#applyAnyNodeClass(candidates) {
|
|
193
|
-
return
|
|
194
|
-
? [candidate]
|
|
195
|
-
: this.scope.allNodeClasses
|
|
196
|
-
.filter((ref) => candidate.requirements.every((required) => this.scope.classExtends(ref, required)))
|
|
197
|
-
.map((ref) => ({ kind: 'exact', ref }))));
|
|
203
|
+
return uniqueBy(candidates.flatMap((candidate) => candidate.kind === 'exact' ? [candidate] : this.#nodeCandidates(candidate)), candidateKey);
|
|
198
204
|
}
|
|
199
205
|
#applyRequirement(candidates, required) {
|
|
200
206
|
const result = [];
|
|
201
207
|
for (const candidate of candidates) {
|
|
202
208
|
if (candidate.kind === 'exact') {
|
|
203
|
-
if (this
|
|
209
|
+
if (this.#classExtends(candidate.ref, required))
|
|
204
210
|
result.push(candidate);
|
|
205
211
|
}
|
|
212
|
+
else if (candidate.kind === 'node') {
|
|
213
|
+
result.push(...this.#inhabited([...candidate.lists, [required]]));
|
|
214
|
+
}
|
|
206
215
|
else {
|
|
207
216
|
result.push({
|
|
208
217
|
kind: 'open',
|
|
209
|
-
requirements:
|
|
218
|
+
requirements: uniqueBy([...candidate.requirements, required], definitionRefIndex),
|
|
210
219
|
});
|
|
211
220
|
}
|
|
212
221
|
}
|
|
213
|
-
return
|
|
222
|
+
return uniqueBy(result, candidateKey);
|
|
214
223
|
}
|
|
215
224
|
#applyEndpoint(candidates, accepts) {
|
|
216
225
|
const result = [];
|
|
@@ -220,18 +229,125 @@ export class PolicyTyping {
|
|
|
220
229
|
result.push(candidate);
|
|
221
230
|
continue;
|
|
222
231
|
}
|
|
232
|
+
if (candidate.kind === 'node') {
|
|
233
|
+
if (accepts.length > 0)
|
|
234
|
+
result.push(...this.#inhabited([...candidate.lists, accepts]));
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
223
237
|
for (const accept of accepts) {
|
|
224
238
|
result.push({
|
|
225
239
|
kind: 'open',
|
|
226
|
-
requirements:
|
|
240
|
+
requirements: uniqueBy([...candidate.requirements, accept], definitionRefIndex),
|
|
227
241
|
});
|
|
228
242
|
}
|
|
229
243
|
}
|
|
230
|
-
return
|
|
244
|
+
return uniqueBy(result, candidateKey);
|
|
231
245
|
}
|
|
232
246
|
#classSatisfiesEndpoint(candidate, accepts) {
|
|
233
|
-
return accepts.some((accept) => this
|
|
247
|
+
return accepts.some((accept) => this.#classExtends(candidate, accept));
|
|
248
|
+
}
|
|
249
|
+
/** Whether `candidate` is the `required` Class or a same-kind descendant. */
|
|
250
|
+
#classExtends(candidate, required) {
|
|
251
|
+
const resolve = (ref) => this.scope.resolveClass(ref);
|
|
252
|
+
const actual = resolve(candidate);
|
|
253
|
+
const target = resolve(required);
|
|
254
|
+
return actual !== undefined && target !== undefined && classSatisfies(actual, target, resolve);
|
|
234
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* The Node Classes of the closure that satisfy one Class of `accepts`, in closure order: those
|
|
258
|
+
* `#classSatisfiesEndpoint` accepts, read from the index rather than tested one by one.
|
|
259
|
+
*/
|
|
260
|
+
#satisfyingOneOf(accepts) {
|
|
261
|
+
const index = this.#nodeClassIndex();
|
|
262
|
+
const positions = new Set();
|
|
263
|
+
for (const accept of accepts) {
|
|
264
|
+
const required = this.scope.resolveClass(accept);
|
|
265
|
+
if (required?.kind !== 'node')
|
|
266
|
+
continue;
|
|
267
|
+
if (isGraphRoot(required))
|
|
268
|
+
return index.nodeClasses;
|
|
269
|
+
for (const position of index.satisfying.get(definitionRefIndex(required.ref)) ?? []) {
|
|
270
|
+
positions.add(position);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return [...positions].sort((left, right) => left - right).map((at) => index.nodeClasses[at]);
|
|
274
|
+
}
|
|
275
|
+
#satisfiesLists(ref, lists) {
|
|
276
|
+
return lists.every((accepts) => this.#classSatisfiesEndpoint(ref, accepts));
|
|
277
|
+
}
|
|
278
|
+
#isNodeClass(ref) {
|
|
279
|
+
return this.#nodeClassIndex().nodes.has(definitionRefIndex(ref));
|
|
280
|
+
}
|
|
281
|
+
/** The Node Classes an open or `node` candidate can be. */
|
|
282
|
+
#nodeCandidates(candidate) {
|
|
283
|
+
return candidate.kind === 'node'
|
|
284
|
+
? [candidate]
|
|
285
|
+
: this.#inhabited(candidate.requirements.map((required) => [required]));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* The `node` candidate of these lists when some Node Class of the closure satisfies them, so that
|
|
289
|
+
* an empty one is dropped where its expansion would have been empty. A witness is almost always a
|
|
290
|
+
* Class the lists name; only when none is does the search read the Classes that satisfy the
|
|
291
|
+
* narrowest list, every witness being one of them.
|
|
292
|
+
*/
|
|
293
|
+
#inhabited(lists) {
|
|
294
|
+
const fits = (ref) => this.#isNodeClass(ref) && this.#satisfiesLists(ref, lists);
|
|
295
|
+
const inhabited = lists.some((accepts) => accepts.some(fits)) ||
|
|
296
|
+
(lists.length === 0
|
|
297
|
+
? this.scope.allNodeClasses
|
|
298
|
+
: lists
|
|
299
|
+
.map((accepts) => this.#satisfyingOneOf(accepts))
|
|
300
|
+
.reduce((narrowest, next) => (next.length < narrowest.length ? next : narrowest))).some(fits);
|
|
301
|
+
return inhabited ? [{ kind: 'node', lists }] : [];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Index each Node Class of the closure under every Class its ancestry reaches, walked as
|
|
306
|
+
* `classSatisfies` walks it: the Class itself, then each declared parent that resolves to a Node
|
|
307
|
+
* Class. A Class then satisfies a non-root Node Class exactly when it is indexed under it.
|
|
308
|
+
* `classSatisfies` compares origin and name; every ClassRef has kind `class`, so the definition
|
|
309
|
+
* index of a ref keys it as that comparison does.
|
|
310
|
+
*/
|
|
311
|
+
function nodeClassIndex(scope) {
|
|
312
|
+
const nodeClasses = [];
|
|
313
|
+
const satisfying = new Map();
|
|
314
|
+
for (const ref of scope.allNodeClasses) {
|
|
315
|
+
const actual = scope.resolveClass(ref);
|
|
316
|
+
if (actual?.kind !== 'node')
|
|
317
|
+
continue;
|
|
318
|
+
const position = nodeClasses.push(ref) - 1;
|
|
319
|
+
for (const key of ancestryKeys(actual, scope)) {
|
|
320
|
+
const positions = satisfying.get(key);
|
|
321
|
+
if (positions === undefined)
|
|
322
|
+
satisfying.set(key, [position]);
|
|
323
|
+
else
|
|
324
|
+
positions.push(position);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
nodeClasses,
|
|
329
|
+
nodes: new Set(nodeClasses.map(definitionRefIndex)),
|
|
330
|
+
concrete: new Set(scope.concreteNodeClasses.map(definitionRefIndex)),
|
|
331
|
+
satisfying,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/** The keys of the Node Classes a Node Class's ancestry reaches, itself included. */
|
|
335
|
+
function ancestryKeys(actual, scope) {
|
|
336
|
+
const seen = new Set();
|
|
337
|
+
const keys = new Set();
|
|
338
|
+
const pending = [actual];
|
|
339
|
+
for (let value = pending.pop(); value !== undefined; value = pending.pop()) {
|
|
340
|
+
if (seen.has(value))
|
|
341
|
+
continue;
|
|
342
|
+
seen.add(value);
|
|
343
|
+
keys.add(definitionRefIndex(value.ref));
|
|
344
|
+
for (const parent of value.extends) {
|
|
345
|
+
const resolved = scope.resolveClass(parent);
|
|
346
|
+
if (resolved?.kind === 'node')
|
|
347
|
+
pending.push(resolved);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return keys;
|
|
235
351
|
}
|
|
236
352
|
function targetContract(term, use) {
|
|
237
353
|
if (use.kind === 'object')
|
|
@@ -262,9 +378,7 @@ function variantsForEdge(use, typing) {
|
|
|
262
378
|
const declaration = typing.scope.edgeDeclaration(edge.class);
|
|
263
379
|
if (declaration === undefined)
|
|
264
380
|
return [];
|
|
265
|
-
const endpoints = declaration
|
|
266
|
-
? [declaration.endpoints.source, declaration.endpoints.target]
|
|
267
|
-
: declaration.endpoints;
|
|
381
|
+
const endpoints = endpointPair(declaration);
|
|
268
382
|
const bounds = edge.repeat ?? { min: 1, max: 1 };
|
|
269
383
|
if (!Number.isInteger(bounds.min) ||
|
|
270
384
|
!Number.isInteger(bounds.max) ||
|
|
@@ -306,7 +420,13 @@ function variantsForEdge(use, typing) {
|
|
|
306
420
|
});
|
|
307
421
|
}
|
|
308
422
|
}
|
|
309
|
-
return
|
|
423
|
+
return uniqueBy(variants, edgeVariantKey);
|
|
424
|
+
}
|
|
425
|
+
/** The two endpoints of an Edge Class, a directed one's as source then target. */
|
|
426
|
+
function endpointPair(declaration) {
|
|
427
|
+
return declaration.orientation === 'directed'
|
|
428
|
+
? [declaration.endpoints.source, declaration.endpoints.target]
|
|
429
|
+
: declaration.endpoints;
|
|
310
430
|
}
|
|
311
431
|
function startEndpoint(endpoints, reversed) {
|
|
312
432
|
return reversed ? endpoints[1] : endpoints[0];
|
|
@@ -314,26 +434,22 @@ function startEndpoint(endpoints, reversed) {
|
|
|
314
434
|
function endEndpoint(endpoints, reversed) {
|
|
315
435
|
return reversed ? endpoints[0] : endpoints[1];
|
|
316
436
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const key = `${value.constraints
|
|
321
|
-
.map(([term, accepts]) => `${term}:${accepts.map(definitionRefIndex).sort().join(',')}`)
|
|
322
|
-
.join('|')}=${value.equalities.map(([left, right]) => `${left}:${right}`).join('|')}`;
|
|
323
|
-
result.set(key, value);
|
|
324
|
-
}
|
|
325
|
-
return [...result.values()];
|
|
437
|
+
/** The values with distinct keys, each at its key's first position and holding its last value. */
|
|
438
|
+
function uniqueBy(values, key) {
|
|
439
|
+
return [...new Map(values.map((value) => [key(value), value])).values()];
|
|
326
440
|
}
|
|
327
|
-
function
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
441
|
+
function edgeVariantKey(variant) {
|
|
442
|
+
const constraints = variant.constraints.map(([term, accepts]) => `${term}:${classSetKey(accepts)}`);
|
|
443
|
+
const equalities = variant.equalities.map(([left, right]) => `${left}:${right}`);
|
|
444
|
+
return `${constraints.join('|')}=${equalities.join('|')}`;
|
|
445
|
+
}
|
|
446
|
+
function candidateKey(candidate) {
|
|
447
|
+
if (candidate.kind === 'exact')
|
|
448
|
+
return `exact:${definitionRefIndex(candidate.ref)}`;
|
|
449
|
+
if (candidate.kind === 'open')
|
|
450
|
+
return `open:${classSetKey(candidate.requirements)}`;
|
|
451
|
+
return `node:${candidate.lists.map(classSetKey).sort().join('|')}`;
|
|
336
452
|
}
|
|
337
|
-
function
|
|
338
|
-
return
|
|
453
|
+
function classSetKey(refs) {
|
|
454
|
+
return refs.map(definitionRefIndex).sort().join(',');
|
|
339
455
|
}
|
|
@@ -90,23 +90,63 @@ export type PolicyObjectRef = ClassRef | FunctionRef | PolicyRef | ViewRef | Cor
|
|
|
90
90
|
export declare function policyObjectKey(ref: PolicyObjectRef): string;
|
|
91
91
|
export declare function policyObjectOrigin(ref: PolicyObjectRef): string;
|
|
92
92
|
export declare function projectedMemberClass(ref: Exclude<PolicyObjectRef, CoreRef>): ClassRef;
|
|
93
|
+
export type ClassObservation = 'read' | 'traverse';
|
|
94
|
+
export type ClassWrite = 'create' | 'update' | 'delete';
|
|
95
|
+
export type ClassOperation = ClassObservation | ClassWrite;
|
|
96
|
+
/**
|
|
97
|
+
* Every Class slot: the observations, which admit every caller when absent, then the writes, which
|
|
98
|
+
* admit only the Domain that declares the Class when absent.
|
|
99
|
+
*/
|
|
100
|
+
export declare const CLASS_OPERATIONS: readonly ClassOperation[];
|
|
101
|
+
export declare function isClassObservation(operation: ClassOperation): operation is ClassObservation;
|
|
93
102
|
/**
|
|
94
103
|
* One Class observation slot: a named Policy, or `false`, the rule no caller satisfies. Its other
|
|
95
104
|
* value has no spelling there: an absent observation Policy already means `true`.
|
|
96
105
|
*/
|
|
97
106
|
export type ClassPolicy = PolicyRef | false;
|
|
98
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* One Class write slot: a named Policy, or `true`, the rule every Identity satisfies. Its other
|
|
109
|
+
* value has no spelling there: an absent write Policy already means the owning Domain only.
|
|
110
|
+
*/
|
|
111
|
+
export type ClassWritePolicy = PolicyRef | true;
|
|
112
|
+
/** What a Class rule may check: the observed or written instance, or one exact projected member. */
|
|
99
113
|
export type ClassRuleObject = Extract<PolicyCheckObject, {
|
|
100
114
|
readonly kind: 'self' | 'ref';
|
|
101
115
|
}>;
|
|
102
116
|
/**
|
|
103
|
-
* Policy checks deciding one Node Class
|
|
104
|
-
* is the same Node for every candidate. A lone check of `self` is spelled as its Policy
|
|
117
|
+
* Policy checks deciding one Node Class slot. `self` is the observed or written Node; a `ref`
|
|
118
|
+
* object is the same Node for every candidate. A lone check of `self` is spelled as its Policy
|
|
119
|
+
* reference.
|
|
105
120
|
*/
|
|
106
121
|
export type ClassRuleChecks = PolicyCheckExpression<PolicyRef, ClassRuleObject>;
|
|
122
|
+
/**
|
|
123
|
+
* Policy checks deciding one Edge Class slot. `self` is the observed or written Edge, checked by a
|
|
124
|
+
* Policy whose source and target are its endpoints; a `ref` object is one exact member, the same
|
|
125
|
+
* Node for every candidate. A lone check of `self` is spelled as its Policy reference.
|
|
126
|
+
*/
|
|
127
|
+
export type EdgeRuleChecks = ClassRuleChecks;
|
|
128
|
+
/** What a Node create rule may check: one exact projected member, since the Node is not yet. */
|
|
129
|
+
export type CreateRuleObject = Extract<PolicyCheckObject, {
|
|
130
|
+
readonly kind: 'ref';
|
|
131
|
+
}>;
|
|
132
|
+
/** Policy checks deciding the creation of a Node: every object is a `ref` member. */
|
|
133
|
+
export type CreateRuleChecks = PolicyCheckExpression<PolicyRef, CreateRuleObject>;
|
|
107
134
|
/** One Node Class observation slot: a Class Policy, or Policy checks over the observed Node. */
|
|
108
135
|
export type NodeClassPolicy = ClassPolicy | ClassRuleChecks;
|
|
109
|
-
|
|
136
|
+
/** One Node Class update or delete slot: a write Policy, or Policy checks over the written Node. */
|
|
137
|
+
export type NodeClassWritePolicy = ClassWritePolicy | ClassRuleChecks;
|
|
138
|
+
/**
|
|
139
|
+
* The Node Class create slot: `true`, or Policy checks over members. A named Policy alone has no
|
|
140
|
+
* object there, so it has no spelling.
|
|
141
|
+
*/
|
|
142
|
+
export type NodeClassCreatePolicy = true | CreateRuleChecks;
|
|
143
|
+
/** One Edge Class observation slot: a Class Policy, or Policy checks over the observed Edge. */
|
|
144
|
+
export type EdgeClassPolicy = ClassPolicy | EdgeRuleChecks;
|
|
145
|
+
/** One Edge Class write slot: a write Policy, or Policy checks over the written Edge. */
|
|
146
|
+
export type EdgeClassWritePolicy = ClassWritePolicy | EdgeRuleChecks;
|
|
147
|
+
/** The value of any Class slot. */
|
|
148
|
+
export type ClassSlotPolicy = PolicyRef | boolean | ClassRuleChecks;
|
|
149
|
+
export declare function isClassRuleChecks(value: ClassSlotPolicy | undefined): value is ClassRuleChecks;
|
|
110
150
|
/**
|
|
111
151
|
* Whether every way Policy checks can hold passes through a check, so that the caller decides each
|
|
112
152
|
* admission. `sameNode` compares two objects and names no caller.
|
|
@@ -33,6 +33,20 @@ export function projectedMemberClass(ref) {
|
|
|
33
33
|
function kernelClassRef(name) {
|
|
34
34
|
return Object.freeze({ origin: 'kernel.astrale.ai', kind: 'class', name });
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Every Class slot: the observations, which admit every caller when absent, then the writes, which
|
|
38
|
+
* admit only the Domain that declares the Class when absent.
|
|
39
|
+
*/
|
|
40
|
+
export const CLASS_OPERATIONS = Object.freeze([
|
|
41
|
+
'read',
|
|
42
|
+
'traverse',
|
|
43
|
+
'create',
|
|
44
|
+
'update',
|
|
45
|
+
'delete',
|
|
46
|
+
]);
|
|
47
|
+
export function isClassObservation(operation) {
|
|
48
|
+
return operation === 'read' || operation === 'traverse';
|
|
49
|
+
}
|
|
36
50
|
export function isClassRuleChecks(value) {
|
|
37
51
|
return (typeof value === 'object' &&
|
|
38
52
|
('check' in value || 'sameNode' in value || 'allOf' in value || 'anyOf' in value));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClassSlotPolicy, PolicyCheckExpression, PolicyExpression, PolicyPatternExpression } from '../model/policy.js';
|
|
2
2
|
export declare function normalizePolicyExpression(value: PolicyExpression): PolicyExpression;
|
|
3
3
|
/**
|
|
4
4
|
* The fixed point of the single pass: a Pattern the single pass reproduces is returned unchanged,
|
|
@@ -6,10 +6,10 @@ export declare function normalizePolicyExpression(value: PolicyExpression): Poli
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function normalizePolicyPattern(value: PolicyPatternExpression): PolicyPatternExpression;
|
|
8
8
|
/**
|
|
9
|
-
* The one spelling of a
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* The one spelling of a Class rule: normalized Policy checks without a composition of one operand,
|
|
10
|
+
* or the Policy that a lone check of `self` names, which is how a Class has always declared one.
|
|
11
|
+
* Callable checks keep their compositions of one operand: their revisions predate this rule. A
|
|
12
|
+
* create rule checks no `self`, so it keeps its checks.
|
|
13
13
|
*/
|
|
14
|
-
export declare function normalizeClassRule(value:
|
|
14
|
+
export declare function normalizeClassRule<Slot extends ClassSlotPolicy>(value: Slot): Slot;
|
|
15
15
|
export declare function normalizePolicyChecks(value: PolicyCheckExpression): PolicyCheckExpression;
|
|
@@ -40,16 +40,16 @@ function normalizePatternOnce(value) {
|
|
|
40
40
|
return normalizePatternNode(rewritten);
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* The one spelling of a
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* The one spelling of a Class rule: normalized Policy checks without a composition of one operand,
|
|
44
|
+
* or the Policy that a lone check of `self` names, which is how a Class has always declared one.
|
|
45
|
+
* Callable checks keep their compositions of one operand: their revisions predate this rule. A
|
|
46
|
+
* create rule checks no `self`, so it keeps its checks.
|
|
47
47
|
*/
|
|
48
48
|
export function normalizeClassRule(value) {
|
|
49
49
|
if (!isClassRuleChecks(value))
|
|
50
50
|
return value;
|
|
51
51
|
const checks = unwrapSingleOperands(normalizePolicyChecks(value));
|
|
52
|
-
return 'check' in checks && checks.object.kind === 'self' ? checks.check : checks;
|
|
52
|
+
return ('check' in checks && checks.object.kind === 'self' ? checks.check : checks);
|
|
53
53
|
}
|
|
54
54
|
/** Replace each composition of one operand by that operand, merging what it then nests. */
|
|
55
55
|
function unwrapSingleOperands(value) {
|
|
@@ -8,19 +8,35 @@ import type { DiagnosticCode } from './diagnostic.js';
|
|
|
8
8
|
* `warning`: a value schema, which the rule reports without refusing it.
|
|
9
9
|
*/
|
|
10
10
|
export type ValueRuleStage = 'schema' | 'context' | 'value' | 'warning';
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The rule one diagnostic code reports, whatever it checks: what `schema.RULES` and `VALUE_RULES`
|
|
13
|
+
* say of every code, each with the stages of its own checks.
|
|
14
|
+
*/
|
|
15
|
+
export interface DiagnosticRule {
|
|
14
16
|
/**
|
|
15
|
-
* The positions the rule constrains, which its diagnostics point at
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* The positions the rule constrains, which its diagnostics point at: JSON Pointer templates in
|
|
18
|
+
* which a `{name}` token stands for one token of the kind it names and `{/path*}` for any number
|
|
19
|
+
* of tokens.
|
|
18
20
|
*/
|
|
19
21
|
readonly positions: readonly [string, ...string[]];
|
|
20
22
|
/** The rule, in one plain sentence. */
|
|
21
23
|
readonly statement: string;
|
|
22
24
|
/** The limits of `schema.limits` the rule enforces, by key, where it enforces some. */
|
|
23
25
|
readonly limits?: readonly [LimitKey, ...LimitKey[]];
|
|
26
|
+
/**
|
|
27
|
+
* The conditions of the published JSON Schema of the document that state the rule, where some
|
|
28
|
+
* do: the URI fragment of each `if` or `not` subschema whose failure refuses the document with
|
|
29
|
+
* this rule's code.
|
|
30
|
+
*/
|
|
31
|
+
readonly conditions?: readonly [string, ...string[]];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The rule one value diagnostic code reports. Its positions are relative to the value schema, the
|
|
35
|
+
* value or the Domain input it checks. A condition refuses a value schema of the document with
|
|
36
|
+
* `DV_VALUE_SCHEMA` and this rule's code as its `valueCode`.
|
|
37
|
+
*/
|
|
38
|
+
export interface ValueRule extends DiagnosticRule {
|
|
39
|
+
readonly stage: ValueRuleStage;
|
|
24
40
|
}
|
|
25
41
|
/**
|
|
26
42
|
* Every value diagnostic code with the rule it reports. A code added to the language does not
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { deepFreeze } from '../freeze.js';
|
|
1
2
|
/**
|
|
2
3
|
* Every value diagnostic code with the rule it reports. A code added to the language does not
|
|
3
4
|
* compile until it is described here.
|
|
4
5
|
*/
|
|
5
|
-
export const VALUE_RULES =
|
|
6
|
+
export const VALUE_RULES = deepFreeze({
|
|
6
7
|
CALLABLE_INPUT_FIELD_UNKNOWN: {
|
|
7
8
|
stage: 'value',
|
|
8
9
|
positions: [''],
|
|
@@ -80,12 +81,7 @@ export const VALUE_RULES = frozen({
|
|
|
80
81
|
},
|
|
81
82
|
VS_REFERENCE_RESOLUTION: {
|
|
82
83
|
stage: 'context',
|
|
83
|
-
positions: [
|
|
84
|
-
'{/path*}/$ref',
|
|
85
|
-
'{/path*}/x-astrale-path/accepts/{index}',
|
|
86
|
-
'',
|
|
87
|
-
'/registry{/path*}',
|
|
88
|
-
],
|
|
84
|
+
positions: ['{/path*}/$ref', '{/path*}/x-astrale-path/accepts/{index}', ''],
|
|
89
85
|
statement: 'A $ref resolves to a location of its own value schema, x-astrale-path accepts Node Classes of the Domain or of a direct dependency, and the schema is checked with the exact dependency closure of its Domain.',
|
|
90
86
|
},
|
|
91
87
|
VS_REGEX_PORTABLE: {
|
|
@@ -103,6 +99,7 @@ export const VALUE_RULES = frozen({
|
|
|
103
99
|
stage: 'schema',
|
|
104
100
|
positions: ['{/path*}/x-astrale-value-kind'],
|
|
105
101
|
statement: 'x-astrale-value-kind declares instant beside type string at any location, or blob as the whole schema of a Property, exactly as values.blob writes it.',
|
|
102
|
+
conditions: ['#/$defs/value-location/anyOf/1/if'],
|
|
106
103
|
},
|
|
107
104
|
VS_VALUE_KIND_JSON: {
|
|
108
105
|
stage: 'warning',
|
|
@@ -110,11 +107,3 @@ export const VALUE_RULES = frozen({
|
|
|
110
107
|
statement: 'A Property of value kind json is filtered by equality only, so its author is warned.',
|
|
111
108
|
},
|
|
112
109
|
});
|
|
113
|
-
function frozen(rules) {
|
|
114
|
-
for (const rule of Object.values(rules)) {
|
|
115
|
-
Object.freeze(Object.freeze(rule).positions);
|
|
116
|
-
if (rule.limits !== undefined)
|
|
117
|
-
Object.freeze(rule.limits);
|
|
118
|
-
}
|
|
119
|
-
return Object.freeze(rules);
|
|
120
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Options } from '@astrale-os/kernel-dsl/value';
|
|
1
2
|
import type { Diagnostic } from '../../validation/diagnostic.js';
|
|
2
3
|
import type { ValueProgram } from '../compile/model.js';
|
|
3
|
-
export declare function validateValueProgram(program: ValueProgram, input: unknown, roots?: readonly number[]): readonly Diagnostic[];
|
|
4
|
+
export declare function validateValueProgram(program: ValueProgram, input: unknown, roots?: readonly number[], options?: Options): readonly Diagnostic[];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { loadValueProgram } from '../compile/load.js';
|
|
2
2
|
import { validateCanonicalJsonDomain } from '../profile/json.validate.js';
|
|
3
3
|
import { evaluateInstance } from './evaluator.js';
|
|
4
|
-
export function validateValueProgram(program, input, roots = [program.root]) {
|
|
5
|
-
const canonical = validateCanonicalJsonDomain(input, '');
|
|
4
|
+
export function validateValueProgram(program, input, roots = [program.root], options) {
|
|
5
|
+
const canonical = validateCanonicalJsonDomain(input, '', options);
|
|
6
6
|
if (canonical.length > 0)
|
|
7
7
|
return canonical;
|
|
8
8
|
return roots.flatMap((root) => {
|