@astrale-os/kernel-dsl 0.2.0-beta.35 → 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 +30 -39
- package/dist/v1/schema/compatibility/meaning.js +102 -530
- 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/package.json +5 -1
|
@@ -1,375 +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 { collectCallableContractRefs, 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
|
-
/**
|
|
63
|
-
* Fingerprint the contract a caller compiles against, independently of its Domain revision: the
|
|
64
|
-
* exact key, the owner, dispatch and abstractness of a Method, and the semantic input and output
|
|
65
|
-
* contracts; authorization is excluded (DM-CALLABLE-FINGERPRINT). A static consumer pins this
|
|
66
|
-
* instead of a Domain revision, so a dependency release that leaves the callable contract untouched
|
|
67
|
-
* keeps every bound invocation valid.
|
|
68
|
-
*/
|
|
69
|
-
export function callableFingerprint(callable) {
|
|
70
|
-
return fingerprintMeaning('owner' in callable
|
|
71
|
-
? {
|
|
72
|
-
kind: 'method',
|
|
73
|
-
key: callable.key,
|
|
74
|
-
owner: callable.owner.key,
|
|
75
|
-
...semanticMethod(callable),
|
|
76
|
-
}
|
|
77
|
-
: { kind: 'function', key: callable.key, ...semanticFunction(callable) });
|
|
78
|
-
}
|
|
79
|
-
function contextOf(root) {
|
|
80
|
-
const schemas = new Map([
|
|
81
|
-
[root.origin, root],
|
|
82
|
-
...(knownDomainClosure(root) ?? []).map(({ schema }) => [schema.origin, schema]),
|
|
83
|
-
]);
|
|
84
|
-
const analyses = new Map();
|
|
85
|
-
return {
|
|
86
|
-
schemas,
|
|
87
|
-
analysis(owner) {
|
|
88
|
-
if (!analyses.has(owner)) {
|
|
89
|
-
const validation = validateDomainSchemaSemantics(owner, lookupForDomainClosure(owner));
|
|
90
|
-
analyses.set(owner, validation.diagnostics.length > 0 ? undefined : validation.analysis.definitions);
|
|
91
|
-
}
|
|
92
|
-
return analyses.get(owner);
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function compareSourceMeanings(source, target, sourceMeanings) {
|
|
97
|
-
// Prove the candidate still has an exact accepted semantic context before comparing it.
|
|
98
|
-
resolve(target);
|
|
99
|
-
const context = contextOf(target);
|
|
100
|
-
const footprint = Object.freeze([...sourceMeanings.keys()].sort(compareUnicode));
|
|
101
|
-
const changes = [];
|
|
102
|
-
const comparisonCoordinates = [];
|
|
103
|
-
for (const key of footprint) {
|
|
104
|
-
const sourceMeaning = sourceMeanings.get(key);
|
|
105
|
-
const targetMeaning = meaningForCoordinate(context, sourceMeaning.coordinate, sourceMeaning.requirement);
|
|
106
|
-
const targetFingerprint = targetMeaning === undefined ? undefined : fingerprintMeaning(targetMeaning);
|
|
107
|
-
comparisonCoordinates.push([
|
|
108
|
-
key,
|
|
109
|
-
sourceMeaning.fingerprint,
|
|
110
|
-
targetFingerprint ?? null,
|
|
111
|
-
]);
|
|
112
|
-
if (targetFingerprint === undefined) {
|
|
113
|
-
changes.push(Object.freeze({ key, kind: 'missing', sourceFingerprint: sourceMeaning.fingerprint }));
|
|
114
|
-
}
|
|
115
|
-
else if (targetFingerprint !== sourceMeaning.fingerprint) {
|
|
116
|
-
changes.push(Object.freeze({
|
|
117
|
-
key,
|
|
118
|
-
kind: 'changed',
|
|
119
|
-
sourceFingerprint: sourceMeaning.fingerprint,
|
|
120
|
-
targetFingerprint,
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
const additions = addedObligations(context, sourceMeanings);
|
|
125
|
-
const sourceRevision = revisionOfDomainSchema(source);
|
|
126
|
-
const targetRevision = revisionOfDomainSchema(target);
|
|
127
|
-
const fingerprint = fingerprintMeaning({
|
|
128
|
-
version: 2,
|
|
129
|
-
dependency: source.origin,
|
|
130
|
-
source: sourceRevision,
|
|
131
|
-
target: targetRevision,
|
|
132
|
-
coordinates: comparisonCoordinates,
|
|
133
|
-
additions: additions.map(({ key, targetFingerprint }) => [key, targetFingerprint]),
|
|
134
|
-
});
|
|
135
|
-
return Object.freeze({
|
|
136
|
-
version: 2,
|
|
137
|
-
dependency: source.origin,
|
|
138
|
-
source: sourceRevision,
|
|
139
|
-
target: targetRevision,
|
|
140
|
-
footprint,
|
|
141
|
-
changes: Object.freeze([...changes, ...additions]),
|
|
142
|
-
fingerprint,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
/** A target may extend an inherited Class only with optional or automatic Properties. */
|
|
146
|
-
function addedObligations(context, sourceMeanings) {
|
|
147
|
-
const additions = new Map();
|
|
148
|
-
for (const { coordinate, requirement } of sourceMeanings.values()) {
|
|
149
|
-
if (coordinate.kind !== 'class' || coordinate.level !== 'inherit' || requirement === undefined)
|
|
150
|
-
continue;
|
|
151
|
-
const owner = context.schemas.get(coordinate.ref.origin);
|
|
152
|
-
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
|
|
153
|
-
if (analysis === undefined)
|
|
154
|
-
continue;
|
|
155
|
-
for (const field of analysis.properties.values()) {
|
|
156
|
-
if (requirement.properties.has(field.identity) || !field.required || field.auto !== undefined)
|
|
157
|
-
continue;
|
|
158
|
-
additions.set(field.identity, Object.freeze({
|
|
159
|
-
key: field.identity,
|
|
160
|
-
kind: 'added',
|
|
161
|
-
targetFingerprint: fingerprintMeaning(propertyMeaning(field)),
|
|
162
|
-
}));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return [...additions.values()].sort((left, right) => compareUnicode(left.key, right.key));
|
|
166
|
-
}
|
|
167
|
-
function collectSchemaRoots(owner) {
|
|
168
|
-
const classes = new Map();
|
|
169
|
-
const members = new Map();
|
|
170
|
-
const add = (ref, site) => {
|
|
171
|
-
if (ref.kind === 'class') {
|
|
172
|
-
const index = definitionRefIndex(ref);
|
|
173
|
-
const level = levelOf(site);
|
|
174
|
-
const previous = classes.get(index);
|
|
175
|
-
if (previous === undefined || (previous.level === 'reference' && level === 'inherit')) {
|
|
176
|
-
classes.set(index, classCoordinate(ref, level));
|
|
177
|
-
}
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
members.set(definitionRefIndex(ref), memberCoordinate(ref));
|
|
181
|
-
};
|
|
182
|
-
for (const declaration of Object.values(owner.classes))
|
|
183
|
-
collectClassRefs(declaration, add);
|
|
184
|
-
for (const declaration of Object.values(owner.functions))
|
|
185
|
-
collectCallableRefs(declaration, add);
|
|
186
|
-
for (const declaration of Object.values(owner.policies)) {
|
|
187
|
-
collectPolicyDefinitionRefs(declaration, add);
|
|
188
|
-
}
|
|
189
|
-
for (const declaration of Object.values(owner.views)) {
|
|
190
|
-
if (declaration.target.kind === 'definition') {
|
|
191
|
-
declaration.target.definitions.forEach((ref) => add(ref, 'view'));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
collectCoreRefs(owner, add);
|
|
195
|
-
return [...classes.values(), ...members.values()].sort((left, right) => compareUnicode(left.key, right.key));
|
|
196
|
-
}
|
|
197
|
-
function collectReachableMeanings(roots, selectedOrigin, context) {
|
|
198
|
-
const meanings = new Map();
|
|
199
|
-
const classLevels = new Map();
|
|
200
|
-
const visitedMembers = new Set();
|
|
201
|
-
const pendingClasses = [];
|
|
202
|
-
const pendingMembers = [];
|
|
203
|
-
const push = (ref, site) => {
|
|
204
|
-
if (ref.kind === 'class')
|
|
205
|
-
pendingClasses.push(classCoordinate(ref, levelOf(site)));
|
|
206
|
-
else
|
|
207
|
-
pendingMembers.push(memberCoordinate(ref));
|
|
208
|
-
};
|
|
209
|
-
for (const coordinate of roots) {
|
|
210
|
-
if (coordinate.kind === 'class') {
|
|
211
|
-
pendingClasses.push(coordinate);
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (coordinate.kind === 'member') {
|
|
215
|
-
pendingMembers.push(coordinate);
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
const meaning = meaningForCoordinate(context, coordinate);
|
|
219
|
-
if (meaning === undefined) {
|
|
220
|
-
throw new SchemaError([
|
|
221
|
-
diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', `Member comparison root ${coordinate.key} does not exist in its source context.`, coordinate.key),
|
|
222
|
-
]);
|
|
223
|
-
}
|
|
224
|
-
retainMeaning(meanings, selectedOrigin, coordinate, meaning);
|
|
225
|
-
collectCoordinateSemanticRefs(context, coordinate, push);
|
|
226
|
-
}
|
|
227
|
-
const visitClass = (coordinate) => {
|
|
228
|
-
const index = definitionRefIndex(coordinate.ref);
|
|
229
|
-
const previous = classLevels.get(index);
|
|
230
|
-
if (previous === 'inherit' || previous === coordinate.level)
|
|
231
|
-
return;
|
|
232
|
-
classLevels.set(index, coordinate.level);
|
|
233
|
-
const owner = requireOwner(context, coordinate.ref, coordinate.key);
|
|
234
|
-
const declaration = owner.classes[coordinate.ref.name];
|
|
235
|
-
const analysis = effectiveDefinition(context, owner, coordinate.ref);
|
|
236
|
-
const resolved = classMeaning(context, coordinate);
|
|
237
|
-
if (declaration === undefined || analysis === undefined || resolved === undefined) {
|
|
238
|
-
throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
|
|
239
|
-
}
|
|
240
|
-
retainMeaning(meanings, selectedOrigin, coordinate, resolved.meaning, resolved.requirement);
|
|
241
|
-
if (previous === undefined) {
|
|
242
|
-
// The surface is level-independent: every effective Property and Method the dependent may
|
|
243
|
-
// read or call, with the Definitions their contracts and policies name.
|
|
244
|
-
for (const field of analysis.properties.values()) {
|
|
245
|
-
retainMeaning(meanings, selectedOrigin, propertyCoordinate(field.owner, field.name), propertyMeaning(field));
|
|
246
|
-
collectValueDefinitionRefs(field.schema, push);
|
|
247
|
-
}
|
|
248
|
-
for (const method of analysis.methods.values()) {
|
|
249
|
-
retainMeaning(meanings, selectedOrigin, methodCoordinate(method.owner, method.name), methodMeaning(method));
|
|
250
|
-
collectCallableContractRefs(method.declaration, push);
|
|
251
|
-
}
|
|
252
|
-
collectClassPolicyRefs(declaration, push);
|
|
253
|
-
}
|
|
254
|
-
if (coordinate.level === 'inherit') {
|
|
255
|
-
// Only inheritance takes on the ancestry and, for an Edge, the endpoint contract.
|
|
256
|
-
declaration.extends.forEach((parent) => push(parent, 'extends'));
|
|
257
|
-
if (declaration.kind === 'edge')
|
|
258
|
-
collectEndpointRefs(declaration, push);
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
const visitMember = (coordinate) => {
|
|
262
|
-
const index = definitionRefIndex(coordinate.ref);
|
|
263
|
-
if (visitedMembers.has(index))
|
|
264
|
-
return;
|
|
265
|
-
visitedMembers.add(index);
|
|
266
|
-
const owner = requireOwner(context, coordinate.ref, coordinate.key);
|
|
267
|
-
const meaning = memberMeaning(owner, coordinate.ref);
|
|
268
|
-
if (meaning === undefined) {
|
|
269
|
-
throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
|
|
270
|
-
}
|
|
271
|
-
retainMeaning(meanings, selectedOrigin, coordinate, meaning);
|
|
272
|
-
collectMemberSemanticRefs(owner, coordinate.ref, push);
|
|
273
|
-
};
|
|
274
|
-
let classIndex = 0;
|
|
275
|
-
let memberIndex = 0;
|
|
276
|
-
while (classIndex < pendingClasses.length || memberIndex < pendingMembers.length) {
|
|
277
|
-
if (classIndex < pendingClasses.length)
|
|
278
|
-
visitClass(pendingClasses[classIndex++]);
|
|
279
|
-
else
|
|
280
|
-
visitMember(pendingMembers[memberIndex++]);
|
|
281
|
-
}
|
|
282
|
-
return meanings;
|
|
283
|
-
}
|
|
284
|
-
function requireOwner(context, ref, key) {
|
|
285
|
-
const owner = context.schemas.get(ref.origin);
|
|
286
|
-
if (owner === undefined) {
|
|
287
|
-
throw contextInvalid(`Retained acceptance context cannot resolve ${key}.`, key);
|
|
288
|
-
}
|
|
289
|
-
return owner;
|
|
290
|
-
}
|
|
291
|
-
function contextInvalid(message, key) {
|
|
292
|
-
return new SchemaError([diagnostic('DM_DEPENDENCY_CONTEXT_INVALID', '', message, key)]);
|
|
293
|
-
}
|
|
294
|
-
function collectCoordinateSemanticRefs(context, coordinate, add) {
|
|
295
|
-
const owner = context.schemas.get(coordinate.owner.origin);
|
|
296
|
-
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.owner);
|
|
297
|
-
if (analysis === undefined)
|
|
298
|
-
return;
|
|
299
|
-
if (coordinate.kind === 'property') {
|
|
300
|
-
const field = analysis.properties.get(coordinate.key);
|
|
301
|
-
if (field !== undefined)
|
|
302
|
-
collectValueDefinitionRefs(field.schema, add);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
const method = [...analysis.methods.values()].find((candidate) => MethodKey.of(candidate.owner, candidate.name) === coordinate.key);
|
|
306
|
-
if (method !== undefined)
|
|
307
|
-
collectCallableContractRefs(method.declaration, add);
|
|
308
|
-
}
|
|
309
|
-
function collectMemberSemanticRefs(owner, ref, add) {
|
|
310
|
-
switch (ref.kind) {
|
|
311
|
-
case 'function': {
|
|
312
|
-
const declaration = owner.functions[ref.name];
|
|
313
|
-
if (declaration !== undefined)
|
|
314
|
-
collectCallableRefs(declaration, add);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
case 'policy': {
|
|
318
|
-
const declaration = owner.policies[ref.name];
|
|
319
|
-
if (declaration !== undefined)
|
|
320
|
-
collectPolicyDefinitionRefs(declaration, add);
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
case 'view': {
|
|
324
|
-
const declaration = owner.views[ref.name];
|
|
325
|
-
if (declaration?.target.kind === 'definition') {
|
|
326
|
-
declaration.target.definitions.forEach((definition) => add(definition, 'view'));
|
|
327
|
-
}
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
case 'core': {
|
|
331
|
-
// The dependency instantiates its own Core; the dependent only refers to it.
|
|
332
|
-
const node = owner.core.nodes[ref.name];
|
|
333
|
-
if (node === undefined)
|
|
334
|
-
return;
|
|
335
|
-
add(node.class);
|
|
336
|
-
for (const edge of incidentCoreEdges(owner, ref.name)) {
|
|
337
|
-
add(edge.class);
|
|
338
|
-
if (edge.source.kind !== 'domain')
|
|
339
|
-
add(edge.source);
|
|
340
|
-
if (edge.target.kind !== 'domain')
|
|
341
|
-
add(edge.target);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
function retainMeaning(meanings, selectedOrigin, coordinate, meaning, requirement) {
|
|
347
|
-
if (Key.origin(coordinate.key) !== selectedOrigin)
|
|
348
|
-
return;
|
|
349
|
-
const fingerprint = fingerprintMeaning(meaning);
|
|
350
|
-
const previous = meanings.get(coordinate.key);
|
|
351
|
-
if (previous !== undefined) {
|
|
352
|
-
const upgraded = previous.coordinate.kind === 'class' &&
|
|
353
|
-
coordinate.kind === 'class' &&
|
|
354
|
-
previous.coordinate.level !== coordinate.level;
|
|
355
|
-
if (upgraded) {
|
|
356
|
-
// A stronger requirement replaces the weaker one for the same Class.
|
|
357
|
-
if (coordinate.level === 'reference')
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
else if (previous.fingerprint !== fingerprint) {
|
|
361
|
-
throw new SchemaError([
|
|
362
|
-
diagnostic('DM_MEANING_INCONSISTENT', '', `Semantic coordinate ${coordinate.key} produced inconsistent effective meaning.`, coordinate.key),
|
|
363
|
-
]);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
meanings.set(coordinate.key, Object.freeze({
|
|
367
|
-
coordinate,
|
|
368
|
-
fingerprint,
|
|
369
|
-
...(requirement === undefined ? {} : { requirement }),
|
|
370
|
-
}));
|
|
371
|
-
}
|
|
372
|
-
function meaningForCoordinate(context, coordinate, requirement) {
|
|
16
|
+
export function meaningForCoordinate(context, coordinate, requirement) {
|
|
373
17
|
const owner = context.schemas.get(Key.origin(coordinate.key));
|
|
374
18
|
if (owner === undefined)
|
|
375
19
|
return undefined;
|
|
@@ -384,16 +28,16 @@ function meaningForCoordinate(context, coordinate, requirement) {
|
|
|
384
28
|
const field = analysis.properties.get(coordinate.key);
|
|
385
29
|
return field === undefined ? undefined : propertyMeaning(field);
|
|
386
30
|
}
|
|
387
|
-
const method =
|
|
31
|
+
const method = effectiveMethod(analysis, coordinate.key);
|
|
388
32
|
return method === undefined ? undefined : methodMeaning(method);
|
|
389
33
|
}
|
|
390
34
|
/**
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
* and
|
|
394
|
-
*
|
|
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.
|
|
395
39
|
*/
|
|
396
|
-
function classMeaning(context, coordinate, onto) {
|
|
40
|
+
export function classMeaning(context, coordinate, onto) {
|
|
397
41
|
const owner = context.schemas.get(coordinate.ref.origin);
|
|
398
42
|
const declaration = owner?.classes[coordinate.ref.name];
|
|
399
43
|
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
|
|
@@ -405,48 +49,39 @@ function classMeaning(context, coordinate, onto) {
|
|
|
405
49
|
properties: new Set(analysis.properties.keys()),
|
|
406
50
|
});
|
|
407
51
|
const meaning = {
|
|
408
|
-
|
|
52
|
+
coordinate: 'class',
|
|
409
53
|
level: coordinate.level,
|
|
410
|
-
|
|
54
|
+
kind: declaration.kind,
|
|
55
|
+
abstract: declaration.abstract,
|
|
411
56
|
...(declaration.kind === 'edge' ? { orientation: declaration.orientation } : {}),
|
|
412
57
|
...(coordinate.level === 'inherit'
|
|
413
58
|
? {
|
|
414
|
-
|
|
59
|
+
extends: onto === undefined
|
|
415
60
|
? ancestors
|
|
416
61
|
: onto.ancestors.filter((ancestor) => ancestors.includes(ancestor)),
|
|
417
62
|
policies: Object.fromEntries(Object.entries(declaration.policies)
|
|
418
63
|
.filter(([, ref]) => ref !== undefined)
|
|
419
64
|
.map(([role, ref]) => [role, keyForDefinitionRef(ref)])),
|
|
420
|
-
|
|
421
|
-
?
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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 }),
|
|
427
75
|
}
|
|
428
76
|
: {}),
|
|
429
77
|
};
|
|
430
|
-
return { meaning, requirement };
|
|
78
|
+
return { meaning: deepFreeze(meaning), requirement };
|
|
431
79
|
}
|
|
432
|
-
function
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
const declaration = context.schemas.get(current.origin)?.classes[current.name];
|
|
436
|
-
if (declaration === undefined)
|
|
437
|
-
return;
|
|
438
|
-
for (const parent of declaration.extends) {
|
|
439
|
-
const key = keyForDefinitionRef(parent);
|
|
440
|
-
if (seen.has(key))
|
|
441
|
-
continue;
|
|
442
|
-
seen.add(key);
|
|
443
|
-
visit(parent);
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
visit(ref);
|
|
447
|
-
return [...seen].sort(compareUnicode);
|
|
80
|
+
export function memberMeaning(owner, ref) {
|
|
81
|
+
const meaning = memberMeaningOf(owner, ref);
|
|
82
|
+
return meaning === undefined ? undefined : deepFreeze(meaning);
|
|
448
83
|
}
|
|
449
|
-
function
|
|
84
|
+
function memberMeaningOf(owner, ref) {
|
|
450
85
|
if (ref.origin !== owner.origin)
|
|
451
86
|
return undefined;
|
|
452
87
|
switch (ref.kind) {
|
|
@@ -454,66 +89,86 @@ function memberMeaning(owner, ref) {
|
|
|
454
89
|
const declaration = owner.functions[ref.name];
|
|
455
90
|
return declaration === undefined
|
|
456
91
|
? undefined
|
|
457
|
-
: {
|
|
92
|
+
: { coordinate: 'function', ...callableContract(declaration) };
|
|
458
93
|
}
|
|
459
94
|
case 'policy': {
|
|
460
95
|
const declaration = owner.policies[ref.name];
|
|
461
96
|
return declaration === undefined
|
|
462
97
|
? undefined
|
|
463
|
-
: {
|
|
98
|
+
: { coordinate: 'policy', expression: declaration.expression };
|
|
464
99
|
}
|
|
465
100
|
case 'view': {
|
|
466
101
|
const declaration = owner.views[ref.name];
|
|
467
102
|
return declaration === undefined
|
|
468
103
|
? undefined
|
|
469
|
-
: {
|
|
104
|
+
: { coordinate: 'view', target: declaration.target };
|
|
470
105
|
}
|
|
471
106
|
case 'core': {
|
|
472
107
|
const node = owner.core.nodes[ref.name];
|
|
473
108
|
return node === undefined
|
|
474
109
|
? undefined
|
|
475
110
|
: {
|
|
476
|
-
|
|
477
|
-
node,
|
|
478
|
-
|
|
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
|
+
})),
|
|
479
120
|
};
|
|
480
121
|
}
|
|
481
122
|
}
|
|
482
123
|
}
|
|
483
|
-
function
|
|
484
|
-
return
|
|
485
|
-
|
|
486
|
-
function propertyMeaning(field) {
|
|
487
|
-
return {
|
|
488
|
-
kind: 'property',
|
|
124
|
+
export function propertyMeaning(field) {
|
|
125
|
+
return deepFreeze({
|
|
126
|
+
coordinate: 'property',
|
|
489
127
|
owner: keyForDefinitionRef(field.owner),
|
|
490
128
|
name: field.name,
|
|
491
129
|
schema: semanticValueSchema(field.schema),
|
|
492
130
|
required: field.required,
|
|
131
|
+
...(field.auto === undefined ? {} : { auto: field.auto }),
|
|
493
132
|
...(field.state === undefined ? {} : { state: field.state }),
|
|
494
|
-
};
|
|
133
|
+
});
|
|
495
134
|
}
|
|
496
|
-
function methodMeaning(method) {
|
|
497
|
-
return {
|
|
498
|
-
|
|
135
|
+
export function methodMeaning(method) {
|
|
136
|
+
return deepFreeze({
|
|
137
|
+
coordinate: 'method',
|
|
499
138
|
owner: keyForDefinitionRef(method.owner),
|
|
500
139
|
name: method.name,
|
|
501
|
-
|
|
502
|
-
};
|
|
140
|
+
...callableContract(method.declaration),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
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) });
|
|
503
161
|
}
|
|
504
|
-
|
|
162
|
+
/** The `always` fields of CALLABLE_FIELDS: dispatch shape and the semantic input and output. */
|
|
163
|
+
function callableContract(declaration) {
|
|
505
164
|
return {
|
|
165
|
+
...('static' in declaration
|
|
166
|
+
? { static: declaration.static, abstract: declaration.abstract }
|
|
167
|
+
: {}),
|
|
506
168
|
input: semanticValueSchema(declaration.input),
|
|
507
169
|
output: semanticOutput(declaration.output),
|
|
508
170
|
};
|
|
509
171
|
}
|
|
510
|
-
function semanticMethod(declaration) {
|
|
511
|
-
return {
|
|
512
|
-
static: declaration.static,
|
|
513
|
-
abstract: declaration.abstract,
|
|
514
|
-
...semanticFunction(declaration),
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
172
|
function semanticOutput(output) {
|
|
518
173
|
switch (output.mode) {
|
|
519
174
|
case 'value':
|
|
@@ -524,20 +179,13 @@ function semanticOutput(output) {
|
|
|
524
179
|
return { mode: 'binary' };
|
|
525
180
|
}
|
|
526
181
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return semantic;
|
|
530
|
-
}
|
|
531
|
-
function semanticView(declaration) {
|
|
532
|
-
const { description: _description, ...semantic } = declaration;
|
|
533
|
-
return semantic;
|
|
534
|
-
}
|
|
535
|
-
function semanticValueSchema(value) {
|
|
182
|
+
/** Drop annotation keywords wherever a schema keyword nests a schema; never inside data. */
|
|
183
|
+
export function semanticValueSchema(value) {
|
|
536
184
|
if (typeof value === 'boolean')
|
|
537
185
|
return value;
|
|
538
186
|
const semantic = {};
|
|
539
187
|
for (const [keyword, entry] of Object.entries(value)) {
|
|
540
|
-
if (
|
|
188
|
+
if (ANNOTATION_KEYWORDS.includes(keyword))
|
|
541
189
|
continue;
|
|
542
190
|
if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(entry)) {
|
|
543
191
|
semantic[keyword] = semanticValueSchema(entry);
|
|
@@ -557,106 +205,30 @@ function semanticValueSchema(value) {
|
|
|
557
205
|
}
|
|
558
206
|
return semantic;
|
|
559
207
|
}
|
|
560
|
-
function
|
|
561
|
-
|
|
562
|
-
try {
|
|
563
|
-
key = Key(input);
|
|
564
|
-
}
|
|
565
|
-
catch {
|
|
566
|
-
return invalidMemberRoot(input, index, 'is not a canonical semantic Key');
|
|
567
|
-
}
|
|
568
|
-
if (Key.origin(key) !== source.origin) {
|
|
569
|
-
return invalidMemberRoot(key, index, `belongs to ${Key.origin(key)}, not source ${source.origin}`);
|
|
570
|
-
}
|
|
571
|
-
const reference = keyReference(key);
|
|
572
|
-
let coordinate;
|
|
573
|
-
if ((reference.member === 'property' || reference.member === 'method') &&
|
|
574
|
-
reference.ownerKind === 'class' &&
|
|
575
|
-
reference.ownerName !== undefined) {
|
|
576
|
-
if (source.classes[reference.ownerName] === undefined) {
|
|
577
|
-
return invalidMemberRoot(key, index, 'has no exact owning Definition');
|
|
578
|
-
}
|
|
579
|
-
const owner = { origin: source.origin, kind: 'class', name: reference.ownerName };
|
|
580
|
-
coordinate =
|
|
581
|
-
reference.member === 'property'
|
|
582
|
-
? propertyCoordinate(owner, reference.name)
|
|
583
|
-
: methodCoordinate(owner, reference.name);
|
|
584
|
-
}
|
|
585
|
-
else if (reference.kind === 'class') {
|
|
586
|
-
if (source.classes[reference.name] === undefined) {
|
|
587
|
-
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
588
|
-
}
|
|
589
|
-
// An explicit Class root compares its surface; it never infers an inheritance contract.
|
|
590
|
-
coordinate = classCoordinate({ origin: source.origin, kind: 'class', name: reference.name }, 'reference');
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
coordinate = memberCoordinate({
|
|
594
|
-
origin: source.origin,
|
|
595
|
-
kind: reference.kind,
|
|
596
|
-
name: reference.name,
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
if (meaningForCoordinate(context, coordinate) === undefined) {
|
|
600
|
-
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
601
|
-
}
|
|
602
|
-
return coordinate;
|
|
603
|
-
}
|
|
604
|
-
function invalidMemberRoot(key, index, reason) {
|
|
605
|
-
throw new SchemaError([
|
|
606
|
-
diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Member comparison root ${JSON.stringify(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
|
|
607
|
-
]);
|
|
608
|
-
}
|
|
609
|
-
function levelOf(site) {
|
|
610
|
-
return site === 'extends' || site === 'core' ? 'inherit' : 'reference';
|
|
611
|
-
}
|
|
612
|
-
function classCoordinate(ref, level) {
|
|
613
|
-
return Object.freeze({ kind: 'class', key: keyForDefinitionRef(ref), ref, level });
|
|
614
|
-
}
|
|
615
|
-
function memberCoordinate(ref) {
|
|
616
|
-
return Object.freeze({ kind: 'member', key: keyForDefinitionRef(ref), ref });
|
|
617
|
-
}
|
|
618
|
-
function propertyCoordinate(owner, name) {
|
|
619
|
-
return Object.freeze({ kind: 'property', key: PropertyKey.of(owner, name), owner, name });
|
|
620
|
-
}
|
|
621
|
-
function methodCoordinate(owner, name) {
|
|
622
|
-
return Object.freeze({ kind: 'method', key: MethodKey.of(owner, name), owner, name });
|
|
623
|
-
}
|
|
624
|
-
function keyForDefinitionRef(ref) {
|
|
625
|
-
return ref.kind === 'core'
|
|
626
|
-
? Key.of(ref)
|
|
627
|
-
: Key.of(ref);
|
|
208
|
+
export function fingerprintMeaning(value) {
|
|
209
|
+
return digestDomainSchemaBytes(json.encode(value));
|
|
628
210
|
}
|
|
629
|
-
function
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
ownerKind: 'class',
|
|
646
|
-
ownerName: parts[1],
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
throw new SchemaError([
|
|
650
|
-
diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', `Key ${key} has no supported V1 shape.`, key),
|
|
651
|
-
]);
|
|
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);
|
|
652
227
|
}
|
|
653
228
|
function incidentCoreEdges(owner, name) {
|
|
654
229
|
return owner.core.edges.filter((edge) => (edge.source.kind === 'core' && edge.source.name === name) ||
|
|
655
230
|
(edge.target.kind === 'core' && edge.target.name === name));
|
|
656
231
|
}
|
|
657
|
-
function fingerprintMeaning(value) {
|
|
658
|
-
return digestDomainSchemaBytes(json.encode(value));
|
|
659
|
-
}
|
|
660
232
|
function isJsonSchema(value) {
|
|
661
233
|
return typeof value === 'boolean' || isJsonObject(value);
|
|
662
234
|
}
|