@astrale-os/kernel-dsl 0.2.0-beta.13 → 0.2.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/v1/builder/class/authoring.d.ts +1 -0
- package/dist/v1/builder/class/authoring.js +2 -2
- package/dist/v1/builder/lowering/class.js +10 -5
- package/dist/v1/compiled/admission/accept.js +4 -4
- package/dist/v1/compiled/admission/semantics.js +33 -12
- package/dist/v1/domain/projection/classes.js +4 -6
- package/dist/v1/language/class/model/class.d.ts +1 -0
- package/dist/v1/language/class/resolved.d.ts +4 -3
- package/dist/v1/schema/resolution/origins.js +4 -6
- package/dist/v1/schema/validation/class.js +24 -1
- package/dist/v1/schema/validation/domain.structure.js +1 -0
- package/dist/v1/schema/validation/policy/uses.js +11 -33
- package/package.json +1 -1
|
@@ -53,6 +53,7 @@ export interface EdgeClassOptions {
|
|
|
53
53
|
readonly extends?: readonly EdgeClassReferenceInput[];
|
|
54
54
|
readonly properties?: PropertyRecordInput;
|
|
55
55
|
readonly policies?: {
|
|
56
|
+
readonly read?: Lazy<Policy>;
|
|
56
57
|
readonly traverse?: Lazy<Policy>;
|
|
57
58
|
};
|
|
58
59
|
readonly constraints?: {
|
|
@@ -114,7 +114,7 @@ function validateEdgeClassOptions(value) {
|
|
|
114
114
|
'constraints',
|
|
115
115
|
]);
|
|
116
116
|
if (value.policies !== undefined)
|
|
117
|
-
assertKnownKeys(value.policies, ['traverse']);
|
|
117
|
+
assertKnownKeys(value.policies, ['read', 'traverse']);
|
|
118
118
|
if (value.constraints !== undefined)
|
|
119
119
|
assertKnownKeys(value.constraints, ['noSelf', 'acyclic']);
|
|
120
120
|
}
|
|
@@ -128,7 +128,7 @@ function validateUndirectedEdgeClassOptions(value) {
|
|
|
128
128
|
'constraints',
|
|
129
129
|
]);
|
|
130
130
|
if (value.policies !== undefined)
|
|
131
|
-
assertKnownKeys(value.policies, ['traverse']);
|
|
131
|
+
assertKnownKeys(value.policies, ['read', 'traverse']);
|
|
132
132
|
if (value.constraints !== undefined)
|
|
133
133
|
assertKnownKeys(value.constraints, ['noSelf']);
|
|
134
134
|
}
|
|
@@ -76,11 +76,16 @@ export class V1DefinitionCompiler {
|
|
|
76
76
|
extends: this.parents(input.extends, 'edge', `${pointer}/extends`),
|
|
77
77
|
properties: facet,
|
|
78
78
|
...contract,
|
|
79
|
-
policies:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
policies: {
|
|
80
|
+
...(input.policies?.read === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: { read: this.policies.ref(input.policies.read, `${pointer}/policies/read`) }),
|
|
83
|
+
...(input.policies?.traverse === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: {
|
|
86
|
+
traverse: this.policies.ref(input.policies.traverse, `${pointer}/policies/traverse`),
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
84
89
|
};
|
|
85
90
|
}
|
|
86
91
|
endpointBase(value, pointer) {
|
|
@@ -166,7 +166,7 @@ function compiledClass(input, values, path) {
|
|
|
166
166
|
if (value.data !== undefined)
|
|
167
167
|
dataDefinition(value.data, `${path}/data`);
|
|
168
168
|
boundedString(value.icon, 1, 8000, 'Class icon', `${path}/icon`);
|
|
169
|
-
classPolicies(value.policies,
|
|
169
|
+
classPolicies(value.policies, `${path}/policies`);
|
|
170
170
|
const methodsPath = `${path}/methods`;
|
|
171
171
|
const methods = array(value.methods, 'Class methods', methodsPath);
|
|
172
172
|
for (const [index, method] of methods.entries())
|
|
@@ -174,7 +174,7 @@ function compiledClass(input, values, path) {
|
|
|
174
174
|
unique(methods.map((method, index) => string(object(method, 'Compiled Method', `${methodsPath}/${index}`).name, 'Method name', `${methodsPath}/${index}/name`)), 'Class Methods', methodsPath);
|
|
175
175
|
}
|
|
176
176
|
else {
|
|
177
|
-
classPolicies(value.policies,
|
|
177
|
+
classPolicies(value.policies, `${path}/policies`);
|
|
178
178
|
edgeContract(value, path);
|
|
179
179
|
}
|
|
180
180
|
return value;
|
|
@@ -313,9 +313,9 @@ function propertyAuto(input, path) {
|
|
|
313
313
|
if (value.set !== 'instant')
|
|
314
314
|
invalid('Property auto setter', undefined, `${path}/set`);
|
|
315
315
|
}
|
|
316
|
-
function classPolicies(input,
|
|
316
|
+
function classPolicies(input, path) {
|
|
317
317
|
const value = object(input, 'Class Policies', path);
|
|
318
|
-
exact(value, [], 'Class Policies', path,
|
|
318
|
+
exact(value, [], 'Class Policies', path, ['read', 'traverse']);
|
|
319
319
|
if (value.read !== undefined)
|
|
320
320
|
definitionRef(value.read, 'policy', 'Class read Policy', `${path}/read`);
|
|
321
321
|
if (value.traverse !== undefined)
|
|
@@ -248,7 +248,29 @@ function edgeRefines(context, child, parent) {
|
|
|
248
248
|
return false;
|
|
249
249
|
const refine = (a, b) => endpointRefines(context, child.endpoints[a], parent.endpoints[b]) &&
|
|
250
250
|
countRefines(child.endpoints[a].incident, parent.endpoints[b].incident);
|
|
251
|
-
|
|
251
|
+
const direct = refine(0, 0) && refine(1, 1);
|
|
252
|
+
if (direct)
|
|
253
|
+
return true;
|
|
254
|
+
const reverse = refine(0, 1) && refine(1, 0);
|
|
255
|
+
return reverse && !hasEffectiveEdgePolicy(context, parent, new Set());
|
|
256
|
+
}
|
|
257
|
+
function hasEffectiveEdgePolicy(context, declaration, visited) {
|
|
258
|
+
if (declaration.policies.read !== undefined || declaration.policies.traverse !== undefined) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
for (const parentRef of declaration.extends) {
|
|
262
|
+
const key = Key.of(parentRef);
|
|
263
|
+
if (visited.has(key))
|
|
264
|
+
continue;
|
|
265
|
+
visited.add(key);
|
|
266
|
+
const parent = context.classes.get(key)?.value;
|
|
267
|
+
if (parent !== undefined &&
|
|
268
|
+
isCompiledEdge(parent) &&
|
|
269
|
+
hasEffectiveEdgePolicy(context, parent, visited)) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
252
274
|
}
|
|
253
275
|
function endpointRefines(context, child, parent) {
|
|
254
276
|
return child.accepts.every((accepted) => classSatisfiesAny(context, accepted, parent.accepts));
|
|
@@ -502,16 +524,15 @@ function validatePolicyParity(context, entry, entryIndex, policies) {
|
|
|
502
524
|
}
|
|
503
525
|
}
|
|
504
526
|
for (const [classIndex, definition] of entry.definitions.classes.entries()) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (traverse !== undefined) {
|
|
527
|
+
for (const operation of ['read', 'traverse']) {
|
|
528
|
+
const policy = definition.policies[operation];
|
|
529
|
+
if (policy === undefined)
|
|
530
|
+
continue;
|
|
531
|
+
const path = `/closure/${entryIndex}/definitions/classes/${classIndex}/policies/${operation}`;
|
|
532
|
+
const label = operation === 'read' ? 'Read' : 'Traverse';
|
|
512
533
|
if (definition.kind === 'node') {
|
|
513
|
-
if (!typing.policyAccepts(
|
|
514
|
-
fail(
|
|
534
|
+
if (!typing.policyAccepts(policy.name, { kind: 'exact', ref: definition.ref })) {
|
|
535
|
+
fail(path, `${label} Policy is incompatible with its protected Node Class.`);
|
|
515
536
|
}
|
|
516
537
|
}
|
|
517
538
|
else if (isCompiledEdge(definition)) {
|
|
@@ -520,8 +541,8 @@ function validatePolicyParity(context, entry, entryIndex, policies) {
|
|
|
520
541
|
: definition.endpoints;
|
|
521
542
|
const sources = typing.concreteEndpointClasses(endpoints[0].accepts);
|
|
522
543
|
const targets = typing.concreteEndpointClasses(endpoints[1].accepts);
|
|
523
|
-
if (sources.some((source) => targets.some((target) => !typing.policyAcceptsEdge(
|
|
524
|
-
fail(
|
|
544
|
+
if (sources.some((source) => targets.some((target) => !typing.policyAcceptsEdge(policy.name, { kind: 'exact', ref: source }, { kind: 'exact', ref: target })))) {
|
|
545
|
+
fail(path, `${label} Policy is incompatible with an accepted Edge endpoint pair.`);
|
|
525
546
|
}
|
|
526
547
|
}
|
|
527
548
|
}
|
|
@@ -25,12 +25,10 @@ export function createClasses(definitions, validation) {
|
|
|
25
25
|
}
|
|
26
26
|
const shell = {};
|
|
27
27
|
const policies = Object.freeze({
|
|
28
|
-
read: Object.freeze(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
])
|
|
33
|
-
: []),
|
|
28
|
+
read: Object.freeze(uniquePolicyRefs([
|
|
29
|
+
...parents.flatMap((parent) => [...parent.policies.read]),
|
|
30
|
+
...(definition.policies.read === undefined ? [] : [definition.policies.read]),
|
|
31
|
+
])),
|
|
34
32
|
traverse: Object.freeze(uniquePolicyRefs([
|
|
35
33
|
...parents.flatMap((parent) => [...parent.policies.traverse]),
|
|
36
34
|
...(definition.policies.traverse === undefined ? [] : [definition.policies.traverse]),
|
|
@@ -9,6 +9,7 @@ export interface NodeClassPolicies {
|
|
|
9
9
|
readonly traverse?: PolicyRef;
|
|
10
10
|
}
|
|
11
11
|
export interface EdgeClassPolicies {
|
|
12
|
+
readonly read?: PolicyRef;
|
|
12
13
|
readonly traverse?: PolicyRef;
|
|
13
14
|
}
|
|
14
15
|
export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet, DataFacet {
|
|
@@ -23,8 +23,9 @@ export interface ResolvedData {
|
|
|
23
23
|
readonly owner: ResolvedClass<'node'>;
|
|
24
24
|
readonly definition: DataDefinition;
|
|
25
25
|
}
|
|
26
|
-
export interface ResolvedClassPolicies
|
|
27
|
-
|
|
26
|
+
export interface ResolvedClassPolicies {
|
|
27
|
+
/** Effective read Policies for this Node or Edge Class. */
|
|
28
|
+
readonly read: readonly PolicyRef[];
|
|
28
29
|
readonly traverse: readonly PolicyRef[];
|
|
29
30
|
}
|
|
30
31
|
export type ResolvedPropertyCollection<ByKey extends Readonly<Record<PropertyKey, ResolvedProperty<unknown>>> = Readonly<Record<PropertyKey, ResolvedProperty<unknown>>>, ByName extends Readonly<Record<string, ResolvedProperty<unknown>>> = Readonly<Record<never, never>>> = Readonly<ByName> & Iterable<ByKey[keyof ByKey]> & {
|
|
@@ -45,7 +46,7 @@ export interface ResolvedClass<Kind extends ClassKind = ClassKind, Properties ex
|
|
|
45
46
|
readonly definition: Definition;
|
|
46
47
|
readonly extends: Parents & readonly ResolvedClass<Kind>[];
|
|
47
48
|
/** Effective inherited Policy conjunction, deduplicated by exact Policy ref. */
|
|
48
|
-
readonly policies: ResolvedClassPolicies
|
|
49
|
+
readonly policies: ResolvedClassPolicies;
|
|
49
50
|
readonly data: Kind extends 'node' ? ResolvedData | undefined : undefined;
|
|
50
51
|
readonly properties: Properties;
|
|
51
52
|
readonly methods: Kind extends 'node' ? InstanceMethods : ResolvedMethodCollection<Record<never, never>>;
|
|
@@ -40,17 +40,15 @@ export function collectDomainDefinitionRefs(schema) {
|
|
|
40
40
|
export function collectClassRefs(declaration, add) {
|
|
41
41
|
declaration.extends.forEach(add);
|
|
42
42
|
Object.values(declaration.properties).forEach((property) => collectValueDefinitionRefs(property.schema, add));
|
|
43
|
+
if (declaration.policies.read !== undefined)
|
|
44
|
+
add(declaration.policies.read);
|
|
45
|
+
if (declaration.policies.traverse !== undefined)
|
|
46
|
+
add(declaration.policies.traverse);
|
|
43
47
|
if (declaration.kind === 'edge') {
|
|
44
48
|
collectEndpointRefs(declaration, add);
|
|
45
|
-
if (declaration.policies.traverse !== undefined)
|
|
46
|
-
add(declaration.policies.traverse);
|
|
47
49
|
return;
|
|
48
50
|
}
|
|
49
51
|
Object.values(declaration.methods).forEach((method) => collectCallableRefs(method, add));
|
|
50
|
-
if (declaration.policies.read !== undefined)
|
|
51
|
-
add(declaration.policies.read);
|
|
52
|
-
if (declaration.policies.traverse !== undefined)
|
|
53
|
-
add(declaration.policies.traverse);
|
|
54
52
|
}
|
|
55
53
|
function collectEndpointRefs(declaration, add) {
|
|
56
54
|
const endpoints = declaration.orientation === 'directed'
|
|
@@ -130,7 +130,30 @@ function edgeContractRefines(environment, child, parent) {
|
|
|
130
130
|
return false;
|
|
131
131
|
const refines = (childIndex, parentIndex) => endpointRefines(environment, child.endpoints[childIndex], parent.endpoints[parentIndex]) &&
|
|
132
132
|
countRefines(child.endpoints[childIndex].incident, parent.endpoints[parentIndex].incident);
|
|
133
|
-
|
|
133
|
+
const direct = refines(0, 0) && refines(1, 1);
|
|
134
|
+
if (direct)
|
|
135
|
+
return true;
|
|
136
|
+
const reverse = refines(0, 1) && refines(1, 0);
|
|
137
|
+
return reverse && !hasEffectiveEdgePolicy(environment, parent, new Set());
|
|
138
|
+
}
|
|
139
|
+
function hasEffectiveEdgePolicy(environment, declaration, visited) {
|
|
140
|
+
if (declaration.policies.read !== undefined || declaration.policies.traverse !== undefined) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
for (const parentRef of declaration.extends) {
|
|
144
|
+
const key = definitionRefIndex(parentRef);
|
|
145
|
+
if (visited.has(key))
|
|
146
|
+
continue;
|
|
147
|
+
visited.add(key);
|
|
148
|
+
const parent = environment.resolveAny(parentRef);
|
|
149
|
+
if (parent !== undefined &&
|
|
150
|
+
parent.ref.kind === 'class' &&
|
|
151
|
+
parent.declaration.kind === 'edge' &&
|
|
152
|
+
hasEffectiveEdgePolicy(environment, parent.declaration, visited)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
134
157
|
}
|
|
135
158
|
function endpointRefines(environment, child, parent) {
|
|
136
159
|
return child.accepts.every((accepted) => classSatisfies(environment, accepted, parent.accepts));
|
|
@@ -203,6 +203,7 @@ const nodeClassPoliciesSchema = z.strictObject({
|
|
|
203
203
|
traverse: policyRefSchema.optional(),
|
|
204
204
|
});
|
|
205
205
|
const edgeClassPoliciesSchema = z.strictObject({
|
|
206
|
+
read: policyRefSchema.optional(),
|
|
206
207
|
traverse: policyRefSchema.optional(),
|
|
207
208
|
});
|
|
208
209
|
const nodeClassSchema = z.strictObject({
|
|
@@ -12,29 +12,17 @@ const PROJECTION_FUNCTION = nodeClassRef('Function');
|
|
|
12
12
|
const PROJECTION_POLICY = nodeClassRef('Policy');
|
|
13
13
|
const PROJECTION_VIEW = nodeClassRef('View');
|
|
14
14
|
export function validateReadUses(context, typing) {
|
|
15
|
-
|
|
16
|
-
if (declaration.kind !== 'node' || declaration.policies.read === undefined) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
const pointer = `/classes/${escapePointer(name)}/policies/read`;
|
|
20
|
-
const ref = declaration.policies.read;
|
|
21
|
-
if (!context.resolvePolicy(ref, pointer))
|
|
22
|
-
continue;
|
|
23
|
-
const object = {
|
|
24
|
-
kind: 'exact',
|
|
25
|
-
ref: { origin: context.environment.candidate.origin, kind: 'class', name },
|
|
26
|
-
};
|
|
27
|
-
if (!typing.policyAccepts(ref.name, object)) {
|
|
28
|
-
context.add('PL_USE', pointer, `${formatDefinitionRef(ref)} is not valid for every normalized branch of protected Class ${name}.`, formatDefinitionRef(ref));
|
|
29
|
-
}
|
|
30
|
-
}
|
|
15
|
+
validateClassPolicyUses(context, typing, 'read');
|
|
31
16
|
}
|
|
32
17
|
export function validateTraverseUses(context, typing) {
|
|
18
|
+
validateClassPolicyUses(context, typing, 'traverse');
|
|
19
|
+
}
|
|
20
|
+
function validateClassPolicyUses(context, typing, operation) {
|
|
33
21
|
for (const [name, declaration] of Object.entries(context.environment.candidate.classes)) {
|
|
34
|
-
const ref = declaration.policies
|
|
22
|
+
const ref = declaration.policies[operation];
|
|
35
23
|
if (ref === undefined)
|
|
36
24
|
continue;
|
|
37
|
-
const pointer = `/classes/${escapePointer(name)}/policies
|
|
25
|
+
const pointer = `/classes/${escapePointer(name)}/policies/${operation}`;
|
|
38
26
|
if (!context.resolvePolicy(ref, pointer))
|
|
39
27
|
continue;
|
|
40
28
|
if (declaration.kind === 'node') {
|
|
@@ -42,9 +30,9 @@ export function validateTraverseUses(context, typing) {
|
|
|
42
30
|
kind: 'exact',
|
|
43
31
|
ref: { origin: context.environment.candidate.origin, kind: 'class', name },
|
|
44
32
|
};
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
}
|
|
33
|
+
if (typing.policyAccepts(ref.name, object))
|
|
34
|
+
continue;
|
|
35
|
+
context.add('PL_USE', pointer, `${formatDefinitionRef(ref)} is not valid for every normalized branch of ${operation} Node Class ${name}.`, formatDefinitionRef(ref));
|
|
48
36
|
continue;
|
|
49
37
|
}
|
|
50
38
|
const endpoints = declaration.orientation === 'directed'
|
|
@@ -52,19 +40,9 @@ export function validateTraverseUses(context, typing) {
|
|
|
52
40
|
: declaration.endpoints;
|
|
53
41
|
const sources = typing.concreteEndpointClasses(endpoints[0].accepts);
|
|
54
42
|
const targets = typing.concreteEndpointClasses(endpoints[1].accepts);
|
|
55
|
-
|
|
56
|
-
for (const source of sources) {
|
|
57
|
-
for (const target of targets) {
|
|
58
|
-
if (!typing.policyAcceptsEdge(ref.name, { kind: 'exact', ref: source }, { kind: 'exact', ref: target })) {
|
|
59
|
-
accepted = false;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!accepted)
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
43
|
+
const accepted = sources.every((source) => targets.every((target) => typing.policyAcceptsEdge(ref.name, { kind: 'exact', ref: source }, { kind: 'exact', ref: target })));
|
|
66
44
|
if (!accepted) {
|
|
67
|
-
context.add('PL_USE', pointer, `${formatDefinitionRef(ref)} is not valid for every concrete source/target pair of
|
|
45
|
+
context.add('PL_USE', pointer, `${formatDefinitionRef(ref)} is not valid for every concrete source/target pair of ${operation} Edge Class ${name}.`, formatDefinitionRef(ref));
|
|
68
46
|
}
|
|
69
47
|
}
|
|
70
48
|
}
|