@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.
@@ -1,358 +1,19 @@
1
1
  import { json } from '@astrale-os/kernel-dsl/value';
2
- import { Key, MethodKey, PropertyKey } from '../../addressing/index.js';
3
- import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
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 { revisionOfDomainSchema } from '../canonical/revision.js';
8
- import { SchemaError, diagnostic } from '../diagnostic.js';
9
- import { knownDomainClosure, lookupForDomainClosure } from '../resolution/closure.js';
10
- import { resolve } from '../resolution/domain.js';
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
- * Compare the exact V1 meaning one accepted Domain used from a retained dependency with a
29
- * candidate revision of that same dependency origin. This operation is pure and makes no
30
- * lifecycle-compatibility decision.
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 compareDependencyMeaning(dependent, target) {
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 = [...analysis.methods.values()].find((candidate) => MethodKey.of(candidate.owner, candidate.name) === coordinate.key);
31
+ const method = effectiveMethod(analysis, coordinate.key);
371
32
  return method === undefined ? undefined : methodMeaning(method);
372
33
  }
373
34
  /**
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.
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
- kind: 'class',
52
+ coordinate: 'class',
392
53
  level: coordinate.level,
393
- classKind: declaration.kind,
54
+ kind: declaration.kind,
55
+ abstract: declaration.abstract,
394
56
  ...(declaration.kind === 'edge' ? { orientation: declaration.orientation } : {}),
395
57
  ...(coordinate.level === 'inherit'
396
58
  ? {
397
- ancestors: onto === undefined
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
- 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 } : {}),
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 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);
80
+ export function memberMeaning(owner, ref) {
81
+ const meaning = memberMeaningOf(owner, ref);
82
+ return meaning === undefined ? undefined : deepFreeze(meaning);
431
83
  }
432
- function memberMeaning(owner, ref) {
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
- : { kind: ref.kind, declaration: semanticFunction(declaration) };
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
- : { kind: ref.kind, declaration: semanticPolicy(declaration) };
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
- : { kind: ref.kind, declaration: semanticView(declaration) };
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
- kind: ref.kind,
460
- node,
461
- edges: incidentCoreEdges(owner, ref.name),
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 effectiveDefinition(context, owner, ref) {
467
- return context.analysis(owner)?.get(definitionRefIndex(ref));
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
- kind: 'method',
135
+ export function methodMeaning(method) {
136
+ return deepFreeze({
137
+ coordinate: 'method',
482
138
  owner: keyForDefinitionRef(method.owner),
483
139
  name: method.name,
484
- declaration: semanticMethod(method.declaration),
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
- function semanticMethod(declaration) {
496
- const { description: _description, ...rest } = declaration;
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
- ...rest,
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
- function semanticPolicy(declaration) {
514
- const { description: _description, ...semantic } = declaration;
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 (VALUE_SCHEMA_ANNOTATIONS.has(keyword))
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 coordinateForKey(context, source, input, index) {
547
- let key;
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 keyReference(key) {
616
- const semantic = key.slice(key.indexOf(':') + 1);
617
- const parts = semantic.split('.');
618
- if (parts[0] === 'core' && parts.length === 2) {
619
- return { kind: 'core', name: parts[1] };
620
- }
621
- if (parts.length === 2) {
622
- return { kind: parts[0], name: parts[1] };
623
- }
624
- if (parts.length === 4 &&
625
- parts[0] === 'class' &&
626
- (parts[2] === 'property' || parts[2] === 'method')) {
627
- return {
628
- kind: 'class',
629
- name: parts[3],
630
- member: parts[2],
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
  }