@astrale-os/kernel-dsl 0.2.0-beta.34 → 0.2.0-beta.36
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/schema/compatibility/compare.d.ts +57 -0
- package/dist/v1/schema/compatibility/compare.js +166 -0
- package/dist/v1/schema/compatibility/context.d.ts +15 -0
- package/dist/v1/schema/compatibility/context.js +43 -0
- package/dist/v1/schema/compatibility/coordinate.d.ts +44 -0
- package/dist/v1/schema/compatibility/coordinate.js +42 -0
- package/dist/v1/schema/compatibility/diff.d.ts +12 -0
- package/dist/v1/schema/compatibility/diff.js +39 -0
- package/dist/v1/schema/compatibility/fields.d.ts +180 -0
- package/dist/v1/schema/compatibility/fields.js +83 -0
- package/dist/v1/schema/compatibility/footprint.d.ts +25 -0
- package/dist/v1/schema/compatibility/footprint.js +263 -0
- package/dist/v1/schema/compatibility/index.d.ts +7 -0
- package/dist/v1/schema/compatibility/index.js +3 -0
- package/dist/v1/schema/compatibility/meaning.d.ts +39 -39
- package/dist/v1/schema/compatibility/meaning.js +102 -516
- package/dist/v1/schema/fingerprint.d.ts +2 -0
- package/dist/v1/schema/fingerprint.js +1 -0
- package/dist/v1/schema/index.d.ts +2 -2
- package/dist/v1/schema/index.js +1 -1
- package/dist/v1/schema/resolution/origins.d.ts +2 -0
- package/dist/v1/schema/resolution/origins.js +6 -2
- package/package.json +5 -1
|
@@ -1,358 +1,19 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
|
-
import { Key
|
|
3
|
-
import { compareUnicode
|
|
2
|
+
import { Key } from '../../addressing/index.js';
|
|
3
|
+
import { compareUnicode } 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';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { collectCallableRefs, collectClassPolicyRefs, collectClassRefs, collectCoreRefs, collectEndpointRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, } from '../resolution/origins.js';
|
|
12
|
-
import { validateDomainSchemaSemantics } from '../validation/domain.js';
|
|
13
|
-
const VALUE_SCHEMA_ANNOTATIONS = new Set([
|
|
14
|
-
'$comment',
|
|
15
|
-
'contentEncoding',
|
|
16
|
-
'contentMediaType',
|
|
17
|
-
'contentSchema',
|
|
18
|
-
'default',
|
|
19
|
-
'deprecated',
|
|
20
|
-
'description',
|
|
21
|
-
'examples',
|
|
22
|
-
'format',
|
|
23
|
-
'readOnly',
|
|
24
|
-
'title',
|
|
25
|
-
'writeOnly',
|
|
26
|
-
]);
|
|
7
|
+
import { deepFreeze } from '../canonical/freeze.js';
|
|
8
|
+
import { effectiveDefinition, effectiveMethod } from './context.js';
|
|
9
|
+
import { keyForDefinitionRef } from './coordinate.js';
|
|
10
|
+
import { ANNOTATION_KEYWORDS } from './fields.js';
|
|
27
11
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
12
|
+
* The meaning of one coordinate is a portable value built from the fields `fields.ts` classifies
|
|
13
|
+
* as counting at the coordinate's usage level; `__tests__/fields.test.ts` proves the two agree.
|
|
14
|
+
* Comparison works on these values, so every difference can name its exact path.
|
|
31
15
|
*/
|
|
32
|
-
export function
|
|
33
|
-
resolve(dependent);
|
|
34
|
-
const context = contextOf(dependent);
|
|
35
|
-
const source = (knownDomainClosure(dependent) ?? []).find((candidate) => candidate.schema.origin === target.origin)?.schema;
|
|
36
|
-
if (source === undefined) {
|
|
37
|
-
throw new SchemaError([
|
|
38
|
-
diagnostic('DM_DEPENDENCY_NOT_RETAINED', '/target/origin', `Domain ${dependent.origin} was not admitted with a dependency at ${target.origin}.`),
|
|
39
|
-
]);
|
|
40
|
-
}
|
|
41
|
-
const meanings = collectReachableMeanings(collectSchemaRoots(dependent), source.origin, context);
|
|
42
|
-
return compareSourceMeanings(source, target, meanings);
|
|
43
|
-
}
|
|
44
|
-
/** Compare one explicit non-empty source member surface and its reachable V1 meaning. */
|
|
45
|
-
export function compareMemberMeaning(source, target, roots) {
|
|
46
|
-
if (source.origin !== target.origin) {
|
|
47
|
-
throw new SchemaError([
|
|
48
|
-
diagnostic('DM_MEMBER_ORIGIN_MISMATCH', '/target/origin', `Member comparison target ${target.origin} does not match source ${source.origin}.`),
|
|
49
|
-
]);
|
|
50
|
-
}
|
|
51
|
-
if (!Array.isArray(roots) || roots.length === 0 || new Set(roots).size !== roots.length) {
|
|
52
|
-
throw new SchemaError([
|
|
53
|
-
diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', 'Member comparison roots must be one non-empty duplicate-free Key set.'),
|
|
54
|
-
]);
|
|
55
|
-
}
|
|
56
|
-
resolve(source);
|
|
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) {
|
|
63
|
-
const schemas = new Map([
|
|
64
|
-
[root.origin, root],
|
|
65
|
-
...(knownDomainClosure(root) ?? []).map(({ schema }) => [schema.origin, schema]),
|
|
66
|
-
]);
|
|
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
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function compareSourceMeanings(source, target, sourceMeanings) {
|
|
80
|
-
// Prove the candidate still has an exact accepted semantic context before comparing it.
|
|
81
|
-
resolve(target);
|
|
82
|
-
const context = contextOf(target);
|
|
83
|
-
const footprint = Object.freeze([...sourceMeanings.keys()].sort(compareUnicode));
|
|
84
|
-
const changes = [];
|
|
85
|
-
const comparisonCoordinates = [];
|
|
86
|
-
for (const key of footprint) {
|
|
87
|
-
const sourceMeaning = sourceMeanings.get(key);
|
|
88
|
-
const targetMeaning = meaningForCoordinate(context, sourceMeaning.coordinate, sourceMeaning.requirement);
|
|
89
|
-
const targetFingerprint = targetMeaning === undefined ? undefined : fingerprintMeaning(targetMeaning);
|
|
90
|
-
comparisonCoordinates.push([
|
|
91
|
-
key,
|
|
92
|
-
sourceMeaning.fingerprint,
|
|
93
|
-
targetFingerprint ?? null,
|
|
94
|
-
]);
|
|
95
|
-
if (targetFingerprint === undefined) {
|
|
96
|
-
changes.push(Object.freeze({ key, kind: 'missing', sourceFingerprint: sourceMeaning.fingerprint }));
|
|
97
|
-
}
|
|
98
|
-
else if (targetFingerprint !== sourceMeaning.fingerprint) {
|
|
99
|
-
changes.push(Object.freeze({
|
|
100
|
-
key,
|
|
101
|
-
kind: 'changed',
|
|
102
|
-
sourceFingerprint: sourceMeaning.fingerprint,
|
|
103
|
-
targetFingerprint,
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const additions = addedObligations(context, sourceMeanings);
|
|
108
|
-
const sourceRevision = revisionOfDomainSchema(source);
|
|
109
|
-
const targetRevision = revisionOfDomainSchema(target);
|
|
110
|
-
const fingerprint = fingerprintMeaning({
|
|
111
|
-
version: 2,
|
|
112
|
-
dependency: source.origin,
|
|
113
|
-
source: sourceRevision,
|
|
114
|
-
target: targetRevision,
|
|
115
|
-
coordinates: comparisonCoordinates,
|
|
116
|
-
additions: additions.map(({ key, targetFingerprint }) => [key, targetFingerprint]),
|
|
117
|
-
});
|
|
118
|
-
return Object.freeze({
|
|
119
|
-
version: 2,
|
|
120
|
-
dependency: source.origin,
|
|
121
|
-
source: sourceRevision,
|
|
122
|
-
target: targetRevision,
|
|
123
|
-
footprint,
|
|
124
|
-
changes: Object.freeze([...changes, ...additions]),
|
|
125
|
-
fingerprint,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
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));
|
|
179
|
-
}
|
|
180
|
-
function collectReachableMeanings(roots, selectedOrigin, context) {
|
|
181
|
-
const meanings = new Map();
|
|
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
|
-
};
|
|
192
|
-
for (const coordinate of roots) {
|
|
193
|
-
if (coordinate.kind === 'class') {
|
|
194
|
-
pendingClasses.push(coordinate);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (coordinate.kind === 'member') {
|
|
198
|
-
pendingMembers.push(coordinate);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
const meaning = meaningForCoordinate(context, coordinate);
|
|
202
|
-
if (meaning === undefined) {
|
|
203
|
-
throw new SchemaError([
|
|
204
|
-
diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', `Member comparison root ${coordinate.key} does not exist in its source context.`, coordinate.key),
|
|
205
|
-
]);
|
|
206
|
-
}
|
|
207
|
-
retainMeaning(meanings, selectedOrigin, coordinate, meaning);
|
|
208
|
-
collectCoordinateSemanticRefs(context, coordinate, push);
|
|
209
|
-
}
|
|
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);
|
|
222
|
-
}
|
|
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);
|
|
236
|
-
}
|
|
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++]);
|
|
264
|
-
}
|
|
265
|
-
return meanings;
|
|
266
|
-
}
|
|
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);
|
|
280
|
-
if (analysis === undefined)
|
|
281
|
-
return;
|
|
282
|
-
if (coordinate.kind === 'property') {
|
|
283
|
-
const field = analysis.properties.get(coordinate.key);
|
|
284
|
-
if (field !== undefined)
|
|
285
|
-
collectValueDefinitionRefs(field.schema, add);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const method = [...analysis.methods.values()].find((candidate) => MethodKey.of(candidate.owner, candidate.name) === coordinate.key);
|
|
289
|
-
if (method !== undefined)
|
|
290
|
-
collectCallableRefs(method.declaration, add);
|
|
291
|
-
}
|
|
292
|
-
function collectMemberSemanticRefs(owner, ref, add) {
|
|
293
|
-
switch (ref.kind) {
|
|
294
|
-
case 'function': {
|
|
295
|
-
const declaration = owner.functions[ref.name];
|
|
296
|
-
if (declaration !== undefined)
|
|
297
|
-
collectCallableRefs(declaration, add);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
case 'policy': {
|
|
301
|
-
const declaration = owner.policies[ref.name];
|
|
302
|
-
if (declaration !== undefined)
|
|
303
|
-
collectPolicyDefinitionRefs(declaration, add);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
case 'view': {
|
|
307
|
-
const declaration = owner.views[ref.name];
|
|
308
|
-
if (declaration?.target.kind === 'definition') {
|
|
309
|
-
declaration.target.definitions.forEach((definition) => add(definition, 'view'));
|
|
310
|
-
}
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
case 'core': {
|
|
314
|
-
// The dependency instantiates its own Core; the dependent only refers to it.
|
|
315
|
-
const node = owner.core.nodes[ref.name];
|
|
316
|
-
if (node === undefined)
|
|
317
|
-
return;
|
|
318
|
-
add(node.class);
|
|
319
|
-
for (const edge of incidentCoreEdges(owner, ref.name)) {
|
|
320
|
-
add(edge.class);
|
|
321
|
-
if (edge.source.kind !== 'domain')
|
|
322
|
-
add(edge.source);
|
|
323
|
-
if (edge.target.kind !== 'domain')
|
|
324
|
-
add(edge.target);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
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) {
|
|
16
|
+
export function meaningForCoordinate(context, coordinate, requirement) {
|
|
356
17
|
const owner = context.schemas.get(Key.origin(coordinate.key));
|
|
357
18
|
if (owner === undefined)
|
|
358
19
|
return undefined;
|
|
@@ -367,16 +28,16 @@ function meaningForCoordinate(context, coordinate, requirement) {
|
|
|
367
28
|
const field = analysis.properties.get(coordinate.key);
|
|
368
29
|
return field === undefined ? undefined : propertyMeaning(field);
|
|
369
30
|
}
|
|
370
|
-
const method =
|
|
31
|
+
const method = effectiveMethod(analysis, coordinate.key);
|
|
371
32
|
return method === undefined ? undefined : methodMeaning(method);
|
|
372
33
|
}
|
|
373
34
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* and
|
|
377
|
-
*
|
|
35
|
+
* A reference needs the Class to exist with its kind, abstractness and orientation; inheritance
|
|
36
|
+
* additionally takes on the source ancestors, class policies, data contract and, for an Edge, the
|
|
37
|
+
* endpoint and constraint contracts. A target projected onto a source requirement keeps only the
|
|
38
|
+
* ancestors the source named, so a superset satisfies it while a removal changes it.
|
|
378
39
|
*/
|
|
379
|
-
function classMeaning(context, coordinate, onto) {
|
|
40
|
+
export function classMeaning(context, coordinate, onto) {
|
|
380
41
|
const owner = context.schemas.get(coordinate.ref.origin);
|
|
381
42
|
const declaration = owner?.classes[coordinate.ref.name];
|
|
382
43
|
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
|
|
@@ -388,48 +49,39 @@ function classMeaning(context, coordinate, onto) {
|
|
|
388
49
|
properties: new Set(analysis.properties.keys()),
|
|
389
50
|
});
|
|
390
51
|
const meaning = {
|
|
391
|
-
|
|
52
|
+
coordinate: 'class',
|
|
392
53
|
level: coordinate.level,
|
|
393
|
-
|
|
54
|
+
kind: declaration.kind,
|
|
55
|
+
abstract: declaration.abstract,
|
|
394
56
|
...(declaration.kind === 'edge' ? { orientation: declaration.orientation } : {}),
|
|
395
57
|
...(coordinate.level === 'inherit'
|
|
396
58
|
? {
|
|
397
|
-
|
|
59
|
+
extends: onto === undefined
|
|
398
60
|
? ancestors
|
|
399
61
|
: onto.ancestors.filter((ancestor) => ancestors.includes(ancestor)),
|
|
400
62
|
policies: Object.fromEntries(Object.entries(declaration.policies)
|
|
401
63
|
.filter(([, ref]) => ref !== undefined)
|
|
402
64
|
.map(([role, ref]) => [role, keyForDefinitionRef(ref)])),
|
|
403
|
-
|
|
404
|
-
?
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
65
|
+
...(declaration.kind === 'node'
|
|
66
|
+
? {
|
|
67
|
+
data: analysis.data === undefined
|
|
68
|
+
? null
|
|
69
|
+
: {
|
|
70
|
+
owner: keyForDefinitionRef(analysis.data.owner),
|
|
71
|
+
declaration: analysis.data.declaration,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
: { endpoints: declaration.endpoints, constraints: declaration.constraints }),
|
|
410
75
|
}
|
|
411
76
|
: {}),
|
|
412
77
|
};
|
|
413
|
-
return { meaning, requirement };
|
|
78
|
+
return { meaning: deepFreeze(meaning), requirement };
|
|
414
79
|
}
|
|
415
|
-
function
|
|
416
|
-
const
|
|
417
|
-
|
|
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);
|
|
80
|
+
export function memberMeaning(owner, ref) {
|
|
81
|
+
const meaning = memberMeaningOf(owner, ref);
|
|
82
|
+
return meaning === undefined ? undefined : deepFreeze(meaning);
|
|
431
83
|
}
|
|
432
|
-
function
|
|
84
|
+
function memberMeaningOf(owner, ref) {
|
|
433
85
|
if (ref.origin !== owner.origin)
|
|
434
86
|
return undefined;
|
|
435
87
|
switch (ref.kind) {
|
|
@@ -437,65 +89,82 @@ function memberMeaning(owner, ref) {
|
|
|
437
89
|
const declaration = owner.functions[ref.name];
|
|
438
90
|
return declaration === undefined
|
|
439
91
|
? undefined
|
|
440
|
-
: {
|
|
92
|
+
: { coordinate: 'function', ...callableContract(declaration) };
|
|
441
93
|
}
|
|
442
94
|
case 'policy': {
|
|
443
95
|
const declaration = owner.policies[ref.name];
|
|
444
96
|
return declaration === undefined
|
|
445
97
|
? undefined
|
|
446
|
-
: {
|
|
98
|
+
: { coordinate: 'policy', expression: declaration.expression };
|
|
447
99
|
}
|
|
448
100
|
case 'view': {
|
|
449
101
|
const declaration = owner.views[ref.name];
|
|
450
102
|
return declaration === undefined
|
|
451
103
|
? undefined
|
|
452
|
-
: {
|
|
104
|
+
: { coordinate: 'view', target: declaration.target };
|
|
453
105
|
}
|
|
454
106
|
case 'core': {
|
|
455
107
|
const node = owner.core.nodes[ref.name];
|
|
456
108
|
return node === undefined
|
|
457
109
|
? undefined
|
|
458
110
|
: {
|
|
459
|
-
|
|
460
|
-
node,
|
|
461
|
-
|
|
111
|
+
coordinate: 'core',
|
|
112
|
+
class: node.class,
|
|
113
|
+
properties: node.properties,
|
|
114
|
+
edges: incidentCoreEdges(owner, ref.name).map((edge) => ({
|
|
115
|
+
class: edge.class,
|
|
116
|
+
source: edge.source,
|
|
117
|
+
target: edge.target,
|
|
118
|
+
properties: edge.properties,
|
|
119
|
+
})),
|
|
462
120
|
};
|
|
463
121
|
}
|
|
464
122
|
}
|
|
465
123
|
}
|
|
466
|
-
function
|
|
467
|
-
return
|
|
468
|
-
|
|
469
|
-
function propertyMeaning(field) {
|
|
470
|
-
return {
|
|
471
|
-
kind: 'property',
|
|
124
|
+
export function propertyMeaning(field) {
|
|
125
|
+
return deepFreeze({
|
|
126
|
+
coordinate: 'property',
|
|
472
127
|
owner: keyForDefinitionRef(field.owner),
|
|
473
128
|
name: field.name,
|
|
474
129
|
schema: semanticValueSchema(field.schema),
|
|
475
130
|
required: field.required,
|
|
131
|
+
...(field.auto === undefined ? {} : { auto: field.auto }),
|
|
476
132
|
...(field.state === undefined ? {} : { state: field.state }),
|
|
477
|
-
};
|
|
133
|
+
});
|
|
478
134
|
}
|
|
479
|
-
function methodMeaning(method) {
|
|
480
|
-
return {
|
|
481
|
-
|
|
135
|
+
export function methodMeaning(method) {
|
|
136
|
+
return deepFreeze({
|
|
137
|
+
coordinate: 'method',
|
|
482
138
|
owner: keyForDefinitionRef(method.owner),
|
|
483
139
|
name: method.name,
|
|
484
|
-
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
function semanticFunction(declaration) {
|
|
488
|
-
const { description: _description, ...rest } = declaration;
|
|
489
|
-
return {
|
|
490
|
-
...rest,
|
|
491
|
-
input: semanticValueSchema(declaration.input),
|
|
492
|
-
output: semanticOutput(declaration.output),
|
|
493
|
-
};
|
|
140
|
+
...callableContract(method.declaration),
|
|
141
|
+
});
|
|
494
142
|
}
|
|
495
|
-
|
|
496
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Fingerprint the contract a caller compiles against, independently of its Domain revision: the
|
|
145
|
+
* exact key, the owner, dispatch and abstractness of a Method, and the semantic input and output
|
|
146
|
+
* contracts; authorization is excluded (DM-CALLABLE-FINGERPRINT). A static consumer pins this
|
|
147
|
+
* instead of a Domain revision, so a dependency release that leaves the callable contract untouched
|
|
148
|
+
* keeps every bound invocation valid.
|
|
149
|
+
*/
|
|
150
|
+
export function callableFingerprint(callable) {
|
|
151
|
+
// This digest input travels in every bound invocation and is recomputed by the Kernel; its
|
|
152
|
+
// shape is frozen, `kind` included, so a Kernel upgrade never invalidates an unchanged pin.
|
|
153
|
+
return fingerprintMeaning('owner' in callable
|
|
154
|
+
? {
|
|
155
|
+
kind: 'method',
|
|
156
|
+
key: callable.key,
|
|
157
|
+
owner: callable.owner.key,
|
|
158
|
+
...callableContract(callable),
|
|
159
|
+
}
|
|
160
|
+
: { kind: 'function', key: callable.key, ...callableContract(callable) });
|
|
161
|
+
}
|
|
162
|
+
/** The `always` fields of CALLABLE_FIELDS: dispatch shape and the semantic input and output. */
|
|
163
|
+
function callableContract(declaration) {
|
|
497
164
|
return {
|
|
498
|
-
...
|
|
165
|
+
...('static' in declaration
|
|
166
|
+
? { static: declaration.static, abstract: declaration.abstract }
|
|
167
|
+
: {}),
|
|
499
168
|
input: semanticValueSchema(declaration.input),
|
|
500
169
|
output: semanticOutput(declaration.output),
|
|
501
170
|
};
|
|
@@ -510,20 +179,13 @@ function semanticOutput(output) {
|
|
|
510
179
|
return { mode: 'binary' };
|
|
511
180
|
}
|
|
512
181
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return semantic;
|
|
516
|
-
}
|
|
517
|
-
function semanticView(declaration) {
|
|
518
|
-
const { description: _description, ...semantic } = declaration;
|
|
519
|
-
return semantic;
|
|
520
|
-
}
|
|
521
|
-
function semanticValueSchema(value) {
|
|
182
|
+
/** Drop annotation keywords wherever a schema keyword nests a schema; never inside data. */
|
|
183
|
+
export function semanticValueSchema(value) {
|
|
522
184
|
if (typeof value === 'boolean')
|
|
523
185
|
return value;
|
|
524
186
|
const semantic = {};
|
|
525
187
|
for (const [keyword, entry] of Object.entries(value)) {
|
|
526
|
-
if (
|
|
188
|
+
if (ANNOTATION_KEYWORDS.includes(keyword))
|
|
527
189
|
continue;
|
|
528
190
|
if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(entry)) {
|
|
529
191
|
semantic[keyword] = semanticValueSchema(entry);
|
|
@@ -543,106 +205,30 @@ function semanticValueSchema(value) {
|
|
|
543
205
|
}
|
|
544
206
|
return semantic;
|
|
545
207
|
}
|
|
546
|
-
function
|
|
547
|
-
|
|
548
|
-
try {
|
|
549
|
-
key = Key(input);
|
|
550
|
-
}
|
|
551
|
-
catch {
|
|
552
|
-
return invalidMemberRoot(input, index, 'is not a canonical semantic Key');
|
|
553
|
-
}
|
|
554
|
-
if (Key.origin(key) !== source.origin) {
|
|
555
|
-
return invalidMemberRoot(key, index, `belongs to ${Key.origin(key)}, not source ${source.origin}`);
|
|
556
|
-
}
|
|
557
|
-
const reference = keyReference(key);
|
|
558
|
-
let coordinate;
|
|
559
|
-
if ((reference.member === 'property' || reference.member === 'method') &&
|
|
560
|
-
reference.ownerKind === 'class' &&
|
|
561
|
-
reference.ownerName !== undefined) {
|
|
562
|
-
if (source.classes[reference.ownerName] === undefined) {
|
|
563
|
-
return invalidMemberRoot(key, index, 'has no exact owning Definition');
|
|
564
|
-
}
|
|
565
|
-
const owner = { origin: source.origin, kind: 'class', name: reference.ownerName };
|
|
566
|
-
coordinate =
|
|
567
|
-
reference.member === 'property'
|
|
568
|
-
? propertyCoordinate(owner, reference.name)
|
|
569
|
-
: methodCoordinate(owner, reference.name);
|
|
570
|
-
}
|
|
571
|
-
else if (reference.kind === 'class') {
|
|
572
|
-
if (source.classes[reference.name] === undefined) {
|
|
573
|
-
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
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');
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
coordinate = memberCoordinate({
|
|
580
|
-
origin: source.origin,
|
|
581
|
-
kind: reference.kind,
|
|
582
|
-
name: reference.name,
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
if (meaningForCoordinate(context, coordinate) === undefined) {
|
|
586
|
-
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
587
|
-
}
|
|
588
|
-
return coordinate;
|
|
589
|
-
}
|
|
590
|
-
function invalidMemberRoot(key, index, reason) {
|
|
591
|
-
throw new SchemaError([
|
|
592
|
-
diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Member comparison root ${JSON.stringify(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
|
|
593
|
-
]);
|
|
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
|
-
}
|
|
601
|
-
function memberCoordinate(ref) {
|
|
602
|
-
return Object.freeze({ kind: 'member', key: keyForDefinitionRef(ref), ref });
|
|
603
|
-
}
|
|
604
|
-
function propertyCoordinate(owner, name) {
|
|
605
|
-
return Object.freeze({ kind: 'property', key: PropertyKey.of(owner, name), owner, name });
|
|
606
|
-
}
|
|
607
|
-
function methodCoordinate(owner, name) {
|
|
608
|
-
return Object.freeze({ kind: 'method', key: MethodKey.of(owner, name), owner, name });
|
|
609
|
-
}
|
|
610
|
-
function keyForDefinitionRef(ref) {
|
|
611
|
-
return ref.kind === 'core'
|
|
612
|
-
? Key.of(ref)
|
|
613
|
-
: Key.of(ref);
|
|
208
|
+
export function fingerprintMeaning(value) {
|
|
209
|
+
return digestDomainSchemaBytes(json.encode(value));
|
|
614
210
|
}
|
|
615
|
-
function
|
|
616
|
-
const
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
ownerKind: 'class',
|
|
632
|
-
ownerName: parts[1],
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
throw new SchemaError([
|
|
636
|
-
diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', `Key ${key} has no supported V1 shape.`, key),
|
|
637
|
-
]);
|
|
211
|
+
function ancestorKeys(context, ref) {
|
|
212
|
+
const seen = new Set();
|
|
213
|
+
const visit = (current) => {
|
|
214
|
+
const declaration = context.schemas.get(current.origin)?.classes[current.name];
|
|
215
|
+
if (declaration === undefined)
|
|
216
|
+
return;
|
|
217
|
+
for (const parent of declaration.extends) {
|
|
218
|
+
const key = keyForDefinitionRef(parent);
|
|
219
|
+
if (seen.has(key))
|
|
220
|
+
continue;
|
|
221
|
+
seen.add(key);
|
|
222
|
+
visit(parent);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
visit(ref);
|
|
226
|
+
return [...seen].sort(compareUnicode);
|
|
638
227
|
}
|
|
639
228
|
function incidentCoreEdges(owner, name) {
|
|
640
229
|
return owner.core.edges.filter((edge) => (edge.source.kind === 'core' && edge.source.name === name) ||
|
|
641
230
|
(edge.target.kind === 'core' && edge.target.name === name));
|
|
642
231
|
}
|
|
643
|
-
function fingerprintMeaning(value) {
|
|
644
|
-
return digestDomainSchemaBytes(json.encode(value));
|
|
645
|
-
}
|
|
646
232
|
function isJsonSchema(value) {
|
|
647
233
|
return typeof value === 'boolean' || isJsonObject(value);
|
|
648
234
|
}
|