@astrale-os/kernel-dsl 0.2.0-beta.32 → 0.2.0-beta.34
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/callable/authoring.d.ts +4 -8
- package/dist/v1/builder/lowering/callable.js +0 -3
- package/dist/v1/builder/lowering/schema/context.js +2 -1
- package/dist/v1/builder/schema/projection.d.ts +10 -13
- package/dist/v1/compiled/admission/accept.js +1 -1
- package/dist/v1/compiled/admission/semantics.js +3 -2
- package/dist/v1/domain/projection/classes.js +4 -13
- package/dist/v1/index.d.ts +1 -1
- package/dist/v1/language/callable/composition.js +2 -2
- package/dist/v1/language/callable/executable.d.ts +6 -0
- package/dist/v1/language/callable/executable.js +4 -0
- package/dist/v1/language/callable/resolved.d.ts +8 -2
- package/dist/v1/language/class/facets.d.ts +3 -1
- package/dist/v1/language/index.d.ts +1 -0
- package/dist/v1/language/index.js +1 -0
- package/dist/v1/language/value/regexp/matcher.d.ts +4 -1
- package/dist/v1/language/value/regexp/matcher.js +114 -29
- package/dist/v1/schema/compatibility/meaning.d.ts +21 -6
- package/dist/v1/schema/compatibility/meaning.js +286 -243
- package/dist/v1/schema/index.d.ts +1 -1
- package/dist/v1/schema/resolution/origins.d.ts +15 -4
- package/dist/v1/schema/resolution/origins.js +31 -24
- package/dist/v1/schema/validation/class.js +2 -1
- package/dist/v1/schema/validation/domain.structure.js +0 -8
- package/dist/v1/schema/validation/policy/objects.js +2 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { Key, MethodKey, PropertyKey } from '../../addressing/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
|
|
4
4
|
import { isValueObject as isJsonObject } from '../../language/value/model/object.js';
|
|
5
5
|
import { SCHEMA_ARRAY_KEYWORDS, SCHEMA_MAP_KEYWORDS, SCHEMA_SINGLE_KEYWORDS, } from '../../language/value/syntax/keywords.js';
|
|
6
6
|
import { digestDomainSchemaBytes } from '../canonical/domain.digest.js';
|
|
@@ -8,7 +8,7 @@ import { revisionOfDomainSchema } from '../canonical/revision.js';
|
|
|
8
8
|
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
9
9
|
import { knownDomainClosure, lookupForDomainClosure } from '../resolution/closure.js';
|
|
10
10
|
import { resolve } from '../resolution/domain.js';
|
|
11
|
-
import { collectCallableRefs, collectClassRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, } from '../resolution/origins.js';
|
|
11
|
+
import { collectCallableRefs, collectClassPolicyRefs, collectClassRefs, collectCoreRefs, collectEndpointRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, } from '../resolution/origins.js';
|
|
12
12
|
import { validateDomainSchemaSemantics } from '../validation/domain.js';
|
|
13
13
|
const VALUE_SCHEMA_ANNOTATIONS = new Set([
|
|
14
14
|
'$comment',
|
|
@@ -31,19 +31,15 @@ const VALUE_SCHEMA_ANNOTATIONS = new Set([
|
|
|
31
31
|
*/
|
|
32
32
|
export function compareDependencyMeaning(dependent, target) {
|
|
33
33
|
resolve(dependent);
|
|
34
|
-
const
|
|
35
|
-
const source =
|
|
34
|
+
const context = contextOf(dependent);
|
|
35
|
+
const source = (knownDomainClosure(dependent) ?? []).find((candidate) => candidate.schema.origin === target.origin)?.schema;
|
|
36
36
|
if (source === undefined) {
|
|
37
37
|
throw new SchemaError([
|
|
38
38
|
diagnostic('DM_DEPENDENCY_NOT_RETAINED', '/target/origin', `Domain ${dependent.origin} was not admitted with a dependency at ${target.origin}.`),
|
|
39
39
|
]);
|
|
40
40
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
...closure.map(({ schema: candidate }) => [candidate.origin, candidate]),
|
|
44
|
-
]);
|
|
45
|
-
const sourceMeanings = collectDependencyMeanings(dependent, source.origin, schemas);
|
|
46
|
-
return compareSourceMeanings(source, target, sourceMeanings);
|
|
41
|
+
const meanings = collectReachableMeanings(collectSchemaRoots(dependent), source.origin, context);
|
|
42
|
+
return compareSourceMeanings(source, target, meanings);
|
|
47
43
|
}
|
|
48
44
|
/** Compare one explicit non-empty source member surface and its reachable V1 meaning. */
|
|
49
45
|
export function compareMemberMeaning(source, target, roots) {
|
|
@@ -58,24 +54,38 @@ export function compareMemberMeaning(source, target, roots) {
|
|
|
58
54
|
]);
|
|
59
55
|
}
|
|
60
56
|
resolve(source);
|
|
61
|
-
const
|
|
57
|
+
const context = contextOf(source);
|
|
58
|
+
const coordinates = roots.map((key, index) => coordinateForKey(context, source, key, index));
|
|
59
|
+
const meanings = collectReachableMeanings(coordinates, source.origin, context);
|
|
60
|
+
return compareSourceMeanings(source, target, meanings);
|
|
61
|
+
}
|
|
62
|
+
function contextOf(root) {
|
|
62
63
|
const schemas = new Map([
|
|
63
|
-
[
|
|
64
|
-
...
|
|
64
|
+
[root.origin, root],
|
|
65
|
+
...(knownDomainClosure(root) ?? []).map(({ schema }) => [schema.origin, schema]),
|
|
65
66
|
]);
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const analyses = new Map();
|
|
68
|
+
return {
|
|
69
|
+
schemas,
|
|
70
|
+
analysis(owner) {
|
|
71
|
+
if (!analyses.has(owner)) {
|
|
72
|
+
const validation = validateDomainSchemaSemantics(owner, lookupForDomainClosure(owner));
|
|
73
|
+
analyses.set(owner, validation.diagnostics.length > 0 ? undefined : validation.analysis.definitions);
|
|
74
|
+
}
|
|
75
|
+
return analyses.get(owner);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
69
78
|
}
|
|
70
79
|
function compareSourceMeanings(source, target, sourceMeanings) {
|
|
71
80
|
// Prove the candidate still has an exact accepted semantic context before comparing it.
|
|
72
81
|
resolve(target);
|
|
82
|
+
const context = contextOf(target);
|
|
73
83
|
const footprint = Object.freeze([...sourceMeanings.keys()].sort(compareUnicode));
|
|
74
84
|
const changes = [];
|
|
75
85
|
const comparisonCoordinates = [];
|
|
76
86
|
for (const key of footprint) {
|
|
77
87
|
const sourceMeaning = sourceMeanings.get(key);
|
|
78
|
-
const targetMeaning = meaningForCoordinate(
|
|
88
|
+
const targetMeaning = meaningForCoordinate(context, sourceMeaning.coordinate, sourceMeaning.requirement);
|
|
79
89
|
const targetFingerprint = targetMeaning === undefined ? undefined : fingerprintMeaning(targetMeaning);
|
|
80
90
|
comparisonCoordinates.push([
|
|
81
91
|
key,
|
|
@@ -83,11 +93,7 @@ function compareSourceMeanings(source, target, sourceMeanings) {
|
|
|
83
93
|
targetFingerprint ?? null,
|
|
84
94
|
]);
|
|
85
95
|
if (targetFingerprint === undefined) {
|
|
86
|
-
changes.push(Object.freeze({
|
|
87
|
-
key,
|
|
88
|
-
kind: 'missing',
|
|
89
|
-
sourceFingerprint: sourceMeaning.fingerprint,
|
|
90
|
-
}));
|
|
96
|
+
changes.push(Object.freeze({ key, kind: 'missing', sourceFingerprint: sourceMeaning.fingerprint }));
|
|
91
97
|
}
|
|
92
98
|
else if (targetFingerprint !== sourceMeaning.fingerprint) {
|
|
93
99
|
changes.push(Object.freeze({
|
|
@@ -98,177 +104,214 @@ function compareSourceMeanings(source, target, sourceMeanings) {
|
|
|
98
104
|
}));
|
|
99
105
|
}
|
|
100
106
|
}
|
|
107
|
+
const additions = addedObligations(context, sourceMeanings);
|
|
101
108
|
const sourceRevision = revisionOfDomainSchema(source);
|
|
102
109
|
const targetRevision = revisionOfDomainSchema(target);
|
|
103
|
-
const immutableChanges = Object.freeze(changes);
|
|
104
110
|
const fingerprint = fingerprintMeaning({
|
|
105
|
-
version:
|
|
111
|
+
version: 2,
|
|
106
112
|
dependency: source.origin,
|
|
107
113
|
source: sourceRevision,
|
|
108
114
|
target: targetRevision,
|
|
109
115
|
coordinates: comparisonCoordinates,
|
|
116
|
+
additions: additions.map(({ key, targetFingerprint }) => [key, targetFingerprint]),
|
|
110
117
|
});
|
|
111
118
|
return Object.freeze({
|
|
112
|
-
version:
|
|
119
|
+
version: 2,
|
|
113
120
|
dependency: source.origin,
|
|
114
121
|
source: sourceRevision,
|
|
115
122
|
target: targetRevision,
|
|
116
123
|
footprint,
|
|
117
|
-
changes:
|
|
124
|
+
changes: Object.freeze([...changes, ...additions]),
|
|
118
125
|
fingerprint,
|
|
119
126
|
});
|
|
120
127
|
}
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
/** A target may extend an inherited Class only with optional or automatic Properties. */
|
|
129
|
+
function addedObligations(context, sourceMeanings) {
|
|
130
|
+
const additions = new Map();
|
|
131
|
+
for (const { coordinate, requirement } of sourceMeanings.values()) {
|
|
132
|
+
if (coordinate.kind !== 'class' || coordinate.level !== 'inherit' || requirement === undefined)
|
|
133
|
+
continue;
|
|
134
|
+
const owner = context.schemas.get(coordinate.ref.origin);
|
|
135
|
+
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
|
|
136
|
+
if (analysis === undefined)
|
|
137
|
+
continue;
|
|
138
|
+
for (const field of analysis.properties.values()) {
|
|
139
|
+
if (requirement.properties.has(field.identity) || !field.required || field.auto !== undefined)
|
|
140
|
+
continue;
|
|
141
|
+
additions.set(field.identity, Object.freeze({
|
|
142
|
+
key: field.identity,
|
|
143
|
+
kind: 'added',
|
|
144
|
+
targetFingerprint: fingerprintMeaning(propertyMeaning(field)),
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return [...additions.values()].sort((left, right) => compareUnicode(left.key, right.key));
|
|
149
|
+
}
|
|
150
|
+
function collectSchemaRoots(owner) {
|
|
151
|
+
const classes = new Map();
|
|
152
|
+
const members = new Map();
|
|
153
|
+
const add = (ref, site) => {
|
|
154
|
+
if (ref.kind === 'class') {
|
|
155
|
+
const index = definitionRefIndex(ref);
|
|
156
|
+
const level = levelOf(site);
|
|
157
|
+
const previous = classes.get(index);
|
|
158
|
+
if (previous === undefined || (previous.level === 'reference' && level === 'inherit')) {
|
|
159
|
+
classes.set(index, classCoordinate(ref, level));
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
members.set(definitionRefIndex(ref), memberCoordinate(ref));
|
|
164
|
+
};
|
|
165
|
+
for (const declaration of Object.values(owner.classes))
|
|
166
|
+
collectClassRefs(declaration, add);
|
|
167
|
+
for (const declaration of Object.values(owner.functions))
|
|
168
|
+
collectCallableRefs(declaration, add);
|
|
169
|
+
for (const declaration of Object.values(owner.policies)) {
|
|
170
|
+
collectPolicyDefinitionRefs(declaration, add);
|
|
171
|
+
}
|
|
172
|
+
for (const declaration of Object.values(owner.views)) {
|
|
173
|
+
if (declaration.target.kind === 'definition') {
|
|
174
|
+
declaration.target.definitions.forEach((ref) => add(ref, 'view'));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
collectCoreRefs(owner, add);
|
|
178
|
+
return [...classes.values(), ...members.values()].sort((left, right) => compareUnicode(left.key, right.key));
|
|
123
179
|
}
|
|
124
|
-
function collectReachableMeanings(roots, selectedOrigin,
|
|
180
|
+
function collectReachableMeanings(roots, selectedOrigin, context) {
|
|
125
181
|
const meanings = new Map();
|
|
126
|
-
const
|
|
127
|
-
const
|
|
182
|
+
const classLevels = new Map();
|
|
183
|
+
const visitedMembers = new Set();
|
|
184
|
+
const pendingClasses = [];
|
|
185
|
+
const pendingMembers = [];
|
|
186
|
+
const push = (ref, site) => {
|
|
187
|
+
if (ref.kind === 'class')
|
|
188
|
+
pendingClasses.push(classCoordinate(ref, levelOf(site)));
|
|
189
|
+
else
|
|
190
|
+
pendingMembers.push(memberCoordinate(ref));
|
|
191
|
+
};
|
|
128
192
|
for (const coordinate of roots) {
|
|
193
|
+
if (coordinate.kind === 'class') {
|
|
194
|
+
pendingClasses.push(coordinate);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
129
197
|
if (coordinate.kind === 'member') {
|
|
130
|
-
|
|
198
|
+
pendingMembers.push(coordinate);
|
|
131
199
|
continue;
|
|
132
200
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
if (owner === undefined || meaning === undefined) {
|
|
201
|
+
const meaning = meaningForCoordinate(context, coordinate);
|
|
202
|
+
if (meaning === undefined) {
|
|
136
203
|
throw new SchemaError([
|
|
137
204
|
diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', `Member comparison root ${coordinate.key} does not exist in its source context.`, coordinate.key),
|
|
138
205
|
]);
|
|
139
206
|
}
|
|
140
207
|
retainMeaning(meanings, selectedOrigin, coordinate, meaning);
|
|
141
|
-
collectCoordinateSemanticRefs(
|
|
208
|
+
collectCoordinateSemanticRefs(context, coordinate, push);
|
|
142
209
|
}
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const owner =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
210
|
+
const visitClass = (coordinate) => {
|
|
211
|
+
const index = definitionRefIndex(coordinate.ref);
|
|
212
|
+
const previous = classLevels.get(index);
|
|
213
|
+
if (previous === 'inherit' || previous === coordinate.level)
|
|
214
|
+
return;
|
|
215
|
+
classLevels.set(index, coordinate.level);
|
|
216
|
+
const owner = requireOwner(context, coordinate.ref, coordinate.key);
|
|
217
|
+
const declaration = owner.classes[coordinate.ref.name];
|
|
218
|
+
const analysis = effectiveDefinition(context, owner, coordinate.ref);
|
|
219
|
+
const resolved = classMeaning(context, coordinate);
|
|
220
|
+
if (declaration === undefined || analysis === undefined || resolved === undefined) {
|
|
221
|
+
throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
|
|
154
222
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
223
|
+
retainMeaning(meanings, selectedOrigin, coordinate, resolved.meaning, resolved.requirement);
|
|
224
|
+
if (previous === undefined) {
|
|
225
|
+
// The surface is level-independent: every effective Property and Method the dependent may
|
|
226
|
+
// read or call, with the Definitions their contracts and policies name.
|
|
227
|
+
for (const field of analysis.properties.values()) {
|
|
228
|
+
retainMeaning(meanings, selectedOrigin, propertyCoordinate(field.owner, field.name), propertyMeaning(field));
|
|
229
|
+
collectValueDefinitionRefs(field.schema, push);
|
|
230
|
+
}
|
|
231
|
+
for (const method of analysis.methods.values()) {
|
|
232
|
+
retainMeaning(meanings, selectedOrigin, methodCoordinate(method.owner, method.name), methodMeaning(method));
|
|
233
|
+
collectCallableRefs(method.declaration, push);
|
|
234
|
+
}
|
|
235
|
+
collectClassPolicyRefs(declaration, push);
|
|
161
236
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
237
|
+
if (coordinate.level === 'inherit') {
|
|
238
|
+
// Only inheritance takes on the ancestry and, for an Edge, the endpoint contract.
|
|
239
|
+
declaration.extends.forEach((parent) => push(parent, 'extends'));
|
|
240
|
+
if (declaration.kind === 'edge')
|
|
241
|
+
collectEndpointRefs(declaration, push);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const visitMember = (coordinate) => {
|
|
245
|
+
const index = definitionRefIndex(coordinate.ref);
|
|
246
|
+
if (visitedMembers.has(index))
|
|
247
|
+
return;
|
|
248
|
+
visitedMembers.add(index);
|
|
249
|
+
const owner = requireOwner(context, coordinate.ref, coordinate.key);
|
|
250
|
+
const meaning = memberMeaning(owner, coordinate.ref);
|
|
251
|
+
if (meaning === undefined) {
|
|
252
|
+
throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
|
|
253
|
+
}
|
|
254
|
+
retainMeaning(meanings, selectedOrigin, coordinate, meaning);
|
|
255
|
+
collectMemberSemanticRefs(owner, coordinate.ref, push);
|
|
256
|
+
};
|
|
257
|
+
let classIndex = 0;
|
|
258
|
+
let memberIndex = 0;
|
|
259
|
+
while (classIndex < pendingClasses.length || memberIndex < pendingMembers.length) {
|
|
260
|
+
if (classIndex < pendingClasses.length)
|
|
261
|
+
visitClass(pendingClasses[classIndex++]);
|
|
262
|
+
else
|
|
263
|
+
visitMember(pendingMembers[memberIndex++]);
|
|
170
264
|
}
|
|
171
265
|
return meanings;
|
|
172
266
|
}
|
|
173
|
-
function
|
|
174
|
-
const
|
|
267
|
+
function requireOwner(context, ref, key) {
|
|
268
|
+
const owner = context.schemas.get(ref.origin);
|
|
269
|
+
if (owner === undefined) {
|
|
270
|
+
throw contextInvalid(`Retained acceptance context cannot resolve ${key}.`, key);
|
|
271
|
+
}
|
|
272
|
+
return owner;
|
|
273
|
+
}
|
|
274
|
+
function contextInvalid(message, key) {
|
|
275
|
+
return new SchemaError([diagnostic('DM_DEPENDENCY_CONTEXT_INVALID', '', message, key)]);
|
|
276
|
+
}
|
|
277
|
+
function collectCoordinateSemanticRefs(context, coordinate, add) {
|
|
278
|
+
const owner = context.schemas.get(coordinate.owner.origin);
|
|
279
|
+
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.owner);
|
|
175
280
|
if (analysis === undefined)
|
|
176
281
|
return;
|
|
177
282
|
if (coordinate.kind === 'property') {
|
|
178
283
|
const field = analysis.properties.get(coordinate.key);
|
|
179
284
|
if (field !== undefined)
|
|
180
|
-
collectValueDefinitionRefs(
|
|
285
|
+
collectValueDefinitionRefs(field.schema, add);
|
|
181
286
|
return;
|
|
182
287
|
}
|
|
183
288
|
const method = [...analysis.methods.values()].find((candidate) => MethodKey.of(candidate.owner, candidate.name) === coordinate.key);
|
|
184
289
|
if (method !== undefined)
|
|
185
|
-
collectCallableRefs(
|
|
186
|
-
}
|
|
187
|
-
function collectEffectiveMeanings(analysis, dependencyOrigin, meanings, add) {
|
|
188
|
-
for (const field of analysis.properties.values()) {
|
|
189
|
-
const coordinate = propertyCoordinate(field.owner, field.name);
|
|
190
|
-
retainMeaning(meanings, dependencyOrigin, coordinate, propertyMeaning(field));
|
|
191
|
-
add(field.owner);
|
|
192
|
-
collectValueDefinitionRefs(semanticValueSchema(field.schema), add);
|
|
193
|
-
}
|
|
194
|
-
for (const method of analysis.methods.values()) {
|
|
195
|
-
const coordinate = methodCoordinate(method.owner, method.name);
|
|
196
|
-
retainMeaning(meanings, dependencyOrigin, coordinate, methodMeaning(method));
|
|
197
|
-
add(method.owner);
|
|
198
|
-
collectCallableRefs(semanticMethod(method.declaration), add);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
function retainMeaning(meanings, dependencyOrigin, coordinate, meaning) {
|
|
202
|
-
if (Key.origin(coordinate.key) !== dependencyOrigin)
|
|
203
|
-
return;
|
|
204
|
-
const fingerprint = fingerprintMeaning(meaning);
|
|
205
|
-
const previous = meanings.get(coordinate.key);
|
|
206
|
-
if (previous !== undefined && previous.fingerprint !== fingerprint) {
|
|
207
|
-
throw new SchemaError([
|
|
208
|
-
diagnostic('DM_MEANING_INCONSISTENT', '', `Semantic coordinate ${coordinate.key} produced inconsistent effective meaning.`, coordinate.key),
|
|
209
|
-
]);
|
|
210
|
-
}
|
|
211
|
-
meanings.set(coordinate.key, Object.freeze({ coordinate, fingerprint }));
|
|
212
|
-
}
|
|
213
|
-
function collectSchemaSemanticRefs(owner) {
|
|
214
|
-
const refs = new Map();
|
|
215
|
-
const add = (ref) => {
|
|
216
|
-
const member = requireMemberRef(ref);
|
|
217
|
-
refs.set(definitionRefIndex(member), member);
|
|
218
|
-
};
|
|
219
|
-
for (const declaration of Object.values(owner.classes)) {
|
|
220
|
-
collectClassRefs(semanticDefinition(declaration), add);
|
|
221
|
-
}
|
|
222
|
-
for (const declaration of Object.values(owner.functions)) {
|
|
223
|
-
collectCallableRefs(semanticFunction(declaration), add);
|
|
224
|
-
}
|
|
225
|
-
for (const declaration of Object.values(owner.policies)) {
|
|
226
|
-
collectPolicyDefinitionRefs(semanticPolicy(declaration), add);
|
|
227
|
-
}
|
|
228
|
-
for (const declaration of Object.values(owner.views)) {
|
|
229
|
-
if (declaration.target.kind === 'definition')
|
|
230
|
-
declaration.target.definitions.forEach(add);
|
|
231
|
-
}
|
|
232
|
-
for (const node of Object.values(owner.core.nodes))
|
|
233
|
-
add(node.class);
|
|
234
|
-
for (const edge of owner.core.edges) {
|
|
235
|
-
add(edge.class);
|
|
236
|
-
if (edge.source.kind !== 'domain')
|
|
237
|
-
add(edge.source);
|
|
238
|
-
if (edge.target.kind !== 'domain')
|
|
239
|
-
add(edge.target);
|
|
240
|
-
}
|
|
241
|
-
return [...refs.values()].sort(compareDefinitionRefs);
|
|
290
|
+
collectCallableRefs(method.declaration, add);
|
|
242
291
|
}
|
|
243
292
|
function collectMemberSemanticRefs(owner, ref, add) {
|
|
244
293
|
switch (ref.kind) {
|
|
245
|
-
case 'class': {
|
|
246
|
-
const declaration = owner.classes[ref.name];
|
|
247
|
-
if (declaration !== undefined) {
|
|
248
|
-
collectClassRefs(semanticDefinition(declaration), add);
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
294
|
case 'function': {
|
|
253
295
|
const declaration = owner.functions[ref.name];
|
|
254
296
|
if (declaration !== undefined)
|
|
255
|
-
collectCallableRefs(
|
|
297
|
+
collectCallableRefs(declaration, add);
|
|
256
298
|
return;
|
|
257
299
|
}
|
|
258
300
|
case 'policy': {
|
|
259
301
|
const declaration = owner.policies[ref.name];
|
|
260
|
-
if (declaration !== undefined)
|
|
261
|
-
collectPolicyDefinitionRefs(
|
|
262
|
-
}
|
|
302
|
+
if (declaration !== undefined)
|
|
303
|
+
collectPolicyDefinitionRefs(declaration, add);
|
|
263
304
|
return;
|
|
264
305
|
}
|
|
265
306
|
case 'view': {
|
|
266
307
|
const declaration = owner.views[ref.name];
|
|
267
|
-
if (declaration?.target.kind === 'definition')
|
|
268
|
-
declaration.target.definitions.forEach(add);
|
|
308
|
+
if (declaration?.target.kind === 'definition') {
|
|
309
|
+
declaration.target.definitions.forEach((definition) => add(definition, 'view'));
|
|
310
|
+
}
|
|
269
311
|
return;
|
|
270
312
|
}
|
|
271
313
|
case 'core': {
|
|
314
|
+
// The dependency instantiates its own Core; the dependent only refers to it.
|
|
272
315
|
const node = owner.core.nodes[ref.name];
|
|
273
316
|
if (node === undefined)
|
|
274
317
|
return;
|
|
@@ -283,12 +326,41 @@ function collectMemberSemanticRefs(owner, ref, add) {
|
|
|
283
326
|
}
|
|
284
327
|
}
|
|
285
328
|
}
|
|
286
|
-
function
|
|
287
|
-
if (Key.origin(coordinate.key) !==
|
|
329
|
+
function retainMeaning(meanings, selectedOrigin, coordinate, meaning, requirement) {
|
|
330
|
+
if (Key.origin(coordinate.key) !== selectedOrigin)
|
|
331
|
+
return;
|
|
332
|
+
const fingerprint = fingerprintMeaning(meaning);
|
|
333
|
+
const previous = meanings.get(coordinate.key);
|
|
334
|
+
if (previous !== undefined) {
|
|
335
|
+
const upgraded = previous.coordinate.kind === 'class' &&
|
|
336
|
+
coordinate.kind === 'class' &&
|
|
337
|
+
previous.coordinate.level !== coordinate.level;
|
|
338
|
+
if (upgraded) {
|
|
339
|
+
// A stronger requirement replaces the weaker one for the same Class.
|
|
340
|
+
if (coordinate.level === 'reference')
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
else if (previous.fingerprint !== fingerprint) {
|
|
344
|
+
throw new SchemaError([
|
|
345
|
+
diagnostic('DM_MEANING_INCONSISTENT', '', `Semantic coordinate ${coordinate.key} produced inconsistent effective meaning.`, coordinate.key),
|
|
346
|
+
]);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
meanings.set(coordinate.key, Object.freeze({
|
|
350
|
+
coordinate,
|
|
351
|
+
fingerprint,
|
|
352
|
+
...(requirement === undefined ? {} : { requirement }),
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
function meaningForCoordinate(context, coordinate, requirement) {
|
|
356
|
+
const owner = context.schemas.get(Key.origin(coordinate.key));
|
|
357
|
+
if (owner === undefined)
|
|
288
358
|
return undefined;
|
|
359
|
+
if (coordinate.kind === 'class')
|
|
360
|
+
return classMeaning(context, coordinate, requirement)?.meaning;
|
|
289
361
|
if (coordinate.kind === 'member')
|
|
290
362
|
return memberMeaning(owner, coordinate.ref);
|
|
291
|
-
const analysis = effectiveDefinition(owner, coordinate.owner);
|
|
363
|
+
const analysis = effectiveDefinition(context, owner, coordinate.owner);
|
|
292
364
|
if (analysis === undefined)
|
|
293
365
|
return undefined;
|
|
294
366
|
if (coordinate.kind === 'property') {
|
|
@@ -298,12 +370,69 @@ function meaningForCoordinate(owner, coordinate) {
|
|
|
298
370
|
const method = [...analysis.methods.values()].find((candidate) => MethodKey.of(candidate.owner, candidate.name) === coordinate.key);
|
|
299
371
|
return method === undefined ? undefined : methodMeaning(method);
|
|
300
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* The requirement one usage level places on a Class. A reference needs the Class to exist with
|
|
375
|
+
* its kind; inheritance additionally needs the source ancestors, class policies, data contract
|
|
376
|
+
* and, for an Edge, the endpoint contract. A target projected onto a source requirement keeps only
|
|
377
|
+
* the ancestors the source named, so a superset satisfies it while a removal changes it.
|
|
378
|
+
*/
|
|
379
|
+
function classMeaning(context, coordinate, onto) {
|
|
380
|
+
const owner = context.schemas.get(coordinate.ref.origin);
|
|
381
|
+
const declaration = owner?.classes[coordinate.ref.name];
|
|
382
|
+
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
|
|
383
|
+
if (owner === undefined || declaration === undefined || analysis === undefined)
|
|
384
|
+
return undefined;
|
|
385
|
+
const ancestors = ancestorKeys(context, coordinate.ref);
|
|
386
|
+
const requirement = Object.freeze({
|
|
387
|
+
ancestors,
|
|
388
|
+
properties: new Set(analysis.properties.keys()),
|
|
389
|
+
});
|
|
390
|
+
const meaning = {
|
|
391
|
+
kind: 'class',
|
|
392
|
+
level: coordinate.level,
|
|
393
|
+
classKind: declaration.kind,
|
|
394
|
+
...(declaration.kind === 'edge' ? { orientation: declaration.orientation } : {}),
|
|
395
|
+
...(coordinate.level === 'inherit'
|
|
396
|
+
? {
|
|
397
|
+
ancestors: onto === undefined
|
|
398
|
+
? ancestors
|
|
399
|
+
: onto.ancestors.filter((ancestor) => ancestors.includes(ancestor)),
|
|
400
|
+
policies: Object.fromEntries(Object.entries(declaration.policies)
|
|
401
|
+
.filter(([, ref]) => ref !== undefined)
|
|
402
|
+
.map(([role, ref]) => [role, keyForDefinitionRef(ref)])),
|
|
403
|
+
data: analysis.data === undefined
|
|
404
|
+
? null
|
|
405
|
+
: {
|
|
406
|
+
owner: keyForDefinitionRef(analysis.data.owner),
|
|
407
|
+
declaration: analysis.data.declaration,
|
|
408
|
+
},
|
|
409
|
+
...(declaration.kind === 'edge' ? { endpoints: declaration.endpoints } : {}),
|
|
410
|
+
}
|
|
411
|
+
: {}),
|
|
412
|
+
};
|
|
413
|
+
return { meaning, requirement };
|
|
414
|
+
}
|
|
415
|
+
function ancestorKeys(context, ref) {
|
|
416
|
+
const seen = new Set();
|
|
417
|
+
const visit = (current) => {
|
|
418
|
+
const declaration = context.schemas.get(current.origin)?.classes[current.name];
|
|
419
|
+
if (declaration === undefined)
|
|
420
|
+
return;
|
|
421
|
+
for (const parent of declaration.extends) {
|
|
422
|
+
const key = keyForDefinitionRef(parent);
|
|
423
|
+
if (seen.has(key))
|
|
424
|
+
continue;
|
|
425
|
+
seen.add(key);
|
|
426
|
+
visit(parent);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
visit(ref);
|
|
430
|
+
return [...seen].sort(compareUnicode);
|
|
431
|
+
}
|
|
301
432
|
function memberMeaning(owner, ref) {
|
|
302
433
|
if (ref.origin !== owner.origin)
|
|
303
434
|
return undefined;
|
|
304
435
|
switch (ref.kind) {
|
|
305
|
-
case 'class':
|
|
306
|
-
return definitionMeaning(owner, ref);
|
|
307
436
|
case 'function': {
|
|
308
437
|
const declaration = owner.functions[ref.name];
|
|
309
438
|
return declaration === undefined
|
|
@@ -334,35 +463,8 @@ function memberMeaning(owner, ref) {
|
|
|
334
463
|
}
|
|
335
464
|
}
|
|
336
465
|
}
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
const effective = effectiveDefinition(owner, ref);
|
|
340
|
-
if (declaration === undefined || effective === undefined || ref.origin !== owner.origin)
|
|
341
|
-
return undefined;
|
|
342
|
-
return {
|
|
343
|
-
kind: ref.kind,
|
|
344
|
-
declaration: semanticDefinition(declaration),
|
|
345
|
-
effective: {
|
|
346
|
-
properties: [...effective.properties.keys()].sort(compareUnicode),
|
|
347
|
-
methods: [...effective.methods.values()]
|
|
348
|
-
.map((method) => MethodKey.of(method.owner, method.name))
|
|
349
|
-
.sort(compareUnicode),
|
|
350
|
-
...(effective.data === undefined
|
|
351
|
-
? {}
|
|
352
|
-
: {
|
|
353
|
-
data: {
|
|
354
|
-
owner: keyForDefinitionRef(effective.data.owner),
|
|
355
|
-
declaration: effective.data.declaration,
|
|
356
|
-
},
|
|
357
|
-
}),
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
function effectiveDefinition(owner, ref) {
|
|
362
|
-
const validation = validateDomainSchemaSemantics(owner, lookupForDomainClosure(owner));
|
|
363
|
-
if (validation.diagnostics.length > 0)
|
|
364
|
-
return undefined;
|
|
365
|
-
return validation.analysis.definitions.get(definitionRefIndex(ref));
|
|
466
|
+
function effectiveDefinition(context, owner, ref) {
|
|
467
|
+
return context.analysis(owner)?.get(definitionRefIndex(ref));
|
|
366
468
|
}
|
|
367
469
|
function propertyMeaning(field) {
|
|
368
470
|
return {
|
|
@@ -382,23 +484,6 @@ function methodMeaning(method) {
|
|
|
382
484
|
declaration: semanticMethod(method.declaration),
|
|
383
485
|
};
|
|
384
486
|
}
|
|
385
|
-
function semanticDefinition(declaration) {
|
|
386
|
-
const { description: _description, ...rest } = declaration;
|
|
387
|
-
const semantic = {
|
|
388
|
-
...rest,
|
|
389
|
-
properties: Object.fromEntries(Object.entries(declaration.properties).map(([name, property]) => [
|
|
390
|
-
name,
|
|
391
|
-
{
|
|
392
|
-
...property,
|
|
393
|
-
schema: semanticValueSchema(property.schema),
|
|
394
|
-
},
|
|
395
|
-
])),
|
|
396
|
-
};
|
|
397
|
-
if ('methods' in declaration) {
|
|
398
|
-
semantic.methods = Object.fromEntries(Object.entries(declaration.methods).map(([name, method]) => [name, semanticMethod(method)]));
|
|
399
|
-
}
|
|
400
|
-
return semantic;
|
|
401
|
-
}
|
|
402
487
|
function semanticFunction(declaration) {
|
|
403
488
|
const { description: _description, ...rest } = declaration;
|
|
404
489
|
return {
|
|
@@ -458,7 +543,7 @@ function semanticValueSchema(value) {
|
|
|
458
543
|
}
|
|
459
544
|
return semantic;
|
|
460
545
|
}
|
|
461
|
-
function coordinateForKey(source, input, index) {
|
|
546
|
+
function coordinateForKey(context, source, input, index) {
|
|
462
547
|
let key;
|
|
463
548
|
try {
|
|
464
549
|
key = Key(input);
|
|
@@ -474,72 +559,45 @@ function coordinateForKey(source, input, index) {
|
|
|
474
559
|
if ((reference.member === 'property' || reference.member === 'method') &&
|
|
475
560
|
reference.ownerKind === 'class' &&
|
|
476
561
|
reference.ownerName !== undefined) {
|
|
477
|
-
|
|
478
|
-
if (owner === undefined) {
|
|
562
|
+
if (source.classes[reference.ownerName] === undefined) {
|
|
479
563
|
return invalidMemberRoot(key, index, 'has no exact owning Definition');
|
|
480
564
|
}
|
|
565
|
+
const owner = { origin: source.origin, kind: 'class', name: reference.ownerName };
|
|
481
566
|
coordinate =
|
|
482
567
|
reference.member === 'property'
|
|
483
568
|
? propertyCoordinate(owner, reference.name)
|
|
484
569
|
: methodCoordinate(owner, reference.name);
|
|
485
570
|
}
|
|
486
|
-
else if (reference.kind
|
|
487
|
-
|
|
488
|
-
origin: source.origin,
|
|
489
|
-
kind: reference.kind,
|
|
490
|
-
name: reference.name,
|
|
491
|
-
});
|
|
492
|
-
if (member === undefined)
|
|
571
|
+
else if (reference.kind === 'class') {
|
|
572
|
+
if (source.classes[reference.name] === undefined) {
|
|
493
573
|
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
494
|
-
|
|
574
|
+
}
|
|
575
|
+
// An explicit Class root compares its surface; it never infers an inheritance contract.
|
|
576
|
+
coordinate = classCoordinate({ origin: source.origin, kind: 'class', name: reference.name }, 'reference');
|
|
495
577
|
}
|
|
496
578
|
else {
|
|
497
579
|
coordinate = memberCoordinate({
|
|
498
580
|
origin: source.origin,
|
|
499
|
-
kind:
|
|
581
|
+
kind: reference.kind,
|
|
500
582
|
name: reference.name,
|
|
501
583
|
});
|
|
502
584
|
}
|
|
503
|
-
if (meaningForCoordinate(
|
|
585
|
+
if (meaningForCoordinate(context, coordinate) === undefined) {
|
|
504
586
|
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
505
587
|
}
|
|
506
588
|
return coordinate;
|
|
507
589
|
}
|
|
508
|
-
function memberRefFor(source, ref) {
|
|
509
|
-
const name = ref.name;
|
|
510
|
-
const owner = definitionRefFor(source, ref.kind, name);
|
|
511
|
-
if (owner !== undefined)
|
|
512
|
-
return owner;
|
|
513
|
-
switch (ref.kind) {
|
|
514
|
-
case 'function':
|
|
515
|
-
return source.functions[name] === undefined
|
|
516
|
-
? undefined
|
|
517
|
-
: { origin: source.origin, kind: 'function', name };
|
|
518
|
-
case 'policy':
|
|
519
|
-
return source.policies[name] === undefined
|
|
520
|
-
? undefined
|
|
521
|
-
: { origin: source.origin, kind: 'policy', name };
|
|
522
|
-
case 'view':
|
|
523
|
-
return source.views[name] === undefined
|
|
524
|
-
? undefined
|
|
525
|
-
: { origin: source.origin, kind: 'view', name };
|
|
526
|
-
default:
|
|
527
|
-
return undefined;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
function definitionRefFor(source, namespace, name) {
|
|
531
|
-
if (namespace === 'class') {
|
|
532
|
-
return source.classes[name] === undefined
|
|
533
|
-
? undefined
|
|
534
|
-
: { origin: source.origin, kind: 'class', name };
|
|
535
|
-
}
|
|
536
|
-
return undefined;
|
|
537
|
-
}
|
|
538
590
|
function invalidMemberRoot(key, index, reason) {
|
|
539
591
|
throw new SchemaError([
|
|
540
592
|
diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Member comparison root ${JSON.stringify(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
|
|
541
593
|
]);
|
|
542
594
|
}
|
|
595
|
+
function levelOf(site) {
|
|
596
|
+
return site === 'extends' || site === 'core' ? 'inherit' : 'reference';
|
|
597
|
+
}
|
|
598
|
+
function classCoordinate(ref, level) {
|
|
599
|
+
return Object.freeze({ kind: 'class', key: keyForDefinitionRef(ref), ref, level });
|
|
600
|
+
}
|
|
543
601
|
function memberCoordinate(ref) {
|
|
544
602
|
return Object.freeze({ kind: 'member', key: keyForDefinitionRef(ref), ref });
|
|
545
603
|
}
|
|
@@ -585,21 +643,6 @@ function incidentCoreEdges(owner, name) {
|
|
|
585
643
|
function fingerprintMeaning(value) {
|
|
586
644
|
return digestDomainSchemaBytes(json.encode(value));
|
|
587
645
|
}
|
|
588
|
-
function isDefinitionRef(ref) {
|
|
589
|
-
return ref.kind === 'class';
|
|
590
|
-
}
|
|
591
|
-
function requireMemberRef(ref) {
|
|
592
|
-
switch (ref.kind) {
|
|
593
|
-
case 'class':
|
|
594
|
-
case 'function':
|
|
595
|
-
case 'policy':
|
|
596
|
-
case 'view':
|
|
597
|
-
return ref;
|
|
598
|
-
case 'core':
|
|
599
|
-
return ref;
|
|
600
|
-
}
|
|
601
|
-
return ref;
|
|
602
|
-
}
|
|
603
646
|
function isJsonSchema(value) {
|
|
604
647
|
return typeof value === 'boolean' || isJsonObject(value);
|
|
605
648
|
}
|