@astrale-os/kernel-dsl 0.2.0-beta.38 → 0.2.0-beta.39

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.
Files changed (58) hide show
  1. package/dist/v1/language/callable/composition.d.ts +13 -1
  2. package/dist/v1/language/callable/composition.js +13 -3
  3. package/dist/v1/language/value/evaluate/evaluator.js +66 -10
  4. package/dist/v1/schema/analysis/class.d.ts +21 -0
  5. package/dist/v1/schema/analysis/class.js +18 -9
  6. package/dist/v1/schema/compatibility/api.d.ts +2 -0
  7. package/dist/v1/schema/compatibility/api.js +1 -0
  8. package/dist/v1/schema/compatibility/comparators/callables.d.ts +28 -0
  9. package/dist/v1/schema/compatibility/comparators/callables.js +77 -0
  10. package/dist/v1/schema/compatibility/comparators/classes.d.ts +28 -0
  11. package/dist/v1/schema/compatibility/comparators/classes.js +96 -0
  12. package/dist/v1/schema/compatibility/comparators/index.d.ts +12 -0
  13. package/dist/v1/schema/compatibility/comparators/index.js +41 -0
  14. package/dist/v1/schema/compatibility/comparators/members.d.ts +4 -0
  15. package/dist/v1/schema/compatibility/comparators/members.js +49 -0
  16. package/dist/v1/schema/compatibility/comparators/values.d.ts +5 -0
  17. package/dist/v1/schema/compatibility/comparators/values.js +37 -0
  18. package/dist/v1/schema/compatibility/compare.d.ts +5 -57
  19. package/dist/v1/schema/compatibility/compare.js +19 -162
  20. package/dist/v1/schema/compatibility/coordinate.d.ts +2 -7
  21. package/dist/v1/schema/compatibility/coordinate.js +2 -6
  22. package/dist/v1/schema/compatibility/evidence.d.ts +5 -0
  23. package/dist/v1/schema/compatibility/evidence.js +51 -0
  24. package/dist/v1/schema/compatibility/fields.d.ts +6 -0
  25. package/dist/v1/schema/compatibility/fields.js +5 -0
  26. package/dist/v1/schema/compatibility/index.d.ts +5 -4
  27. package/dist/v1/schema/compatibility/index.js +3 -2
  28. package/dist/v1/schema/compatibility/legacy/index.d.ts +58 -0
  29. package/dist/v1/schema/compatibility/legacy/index.js +145 -0
  30. package/dist/v1/schema/compatibility/meaning/footprint.d.ts +7 -0
  31. package/dist/v1/schema/compatibility/meaning/footprint.js +57 -0
  32. package/dist/v1/schema/compatibility/meaning/index.d.ts +3 -0
  33. package/dist/v1/schema/compatibility/meaning/index.js +14 -0
  34. package/dist/v1/schema/compatibility/model.d.ts +96 -0
  35. package/dist/v1/schema/compatibility/model.js +1 -0
  36. package/dist/v1/schema/compatibility/references.d.ts +30 -0
  37. package/dist/v1/schema/compatibility/references.js +122 -0
  38. package/dist/v1/schema/compatibility/report.d.ts +26 -0
  39. package/dist/v1/schema/compatibility/report.js +100 -0
  40. package/dist/v1/schema/compatibility/scope.d.ts +27 -0
  41. package/dist/v1/schema/compatibility/scope.js +78 -0
  42. package/dist/v1/schema/compatibility/structure/index.d.ts +3 -0
  43. package/dist/v1/schema/compatibility/structure/index.js +16 -0
  44. package/dist/v1/schema/compatibility/structure/inheritance.d.ts +7 -0
  45. package/dist/v1/schema/compatibility/structure/inheritance.js +68 -0
  46. package/dist/v1/schema/compatibility/structure/requirements.d.ts +7 -0
  47. package/dist/v1/schema/compatibility/structure/requirements.js +70 -0
  48. package/dist/v1/schema/compatibility/subjects.d.ts +34 -0
  49. package/dist/v1/schema/compatibility/subjects.js +86 -0
  50. package/dist/v1/schema/fingerprint.d.ts +2 -2
  51. package/dist/v1/schema/fingerprint.js +1 -1
  52. package/dist/v1/schema/index.d.ts +3 -0
  53. package/dist/v1/schema/index.js +2 -0
  54. package/package.json +2 -2
  55. package/dist/v1/schema/compatibility/footprint.d.ts +0 -25
  56. package/dist/v1/schema/compatibility/footprint.js +0 -264
  57. package/dist/v1/schema/compatibility/meaning.d.ts +0 -44
  58. package/dist/v1/schema/compatibility/meaning.js +0 -234
@@ -10,6 +10,18 @@ export interface MethodComposition<Method> {
10
10
  readonly frontier: ReadonlyMap<string, Method>;
11
11
  readonly methods: ReadonlyMap<string, Method>;
12
12
  }
13
+ /** Structured failure facts; human diagnostic wording is not compatibility evidence. */
14
+ export type MethodCompositionIssue = {
15
+ readonly kind: 'signature';
16
+ readonly local: string;
17
+ readonly required: string;
18
+ } | {
19
+ readonly kind: 'ambiguous';
20
+ readonly methods: readonly [string, string];
21
+ } | {
22
+ readonly kind: 'missing';
23
+ readonly required: string;
24
+ };
13
25
  /** Concrete implementations never discharge a descendant's abstract obligations. */
14
- export declare function composeMethods<Method extends MethodMember>(parents: readonly MethodComposition<Method>[], own: ReadonlyMap<string, Method>, abstract: boolean, sameSignature: (left: Method, right: Method) => boolean, report: (message: string) => void): MethodComposition<Method>;
26
+ export declare function composeMethods<Method extends MethodMember>(parents: readonly MethodComposition<Method>[], own: ReadonlyMap<string, Method>, abstract: boolean, sameSignature: (left: Method, right: Method) => boolean, report: (message: string, issue: MethodCompositionIssue) => void): MethodComposition<Method>;
15
27
  export {};
@@ -5,7 +5,11 @@ export function composeMethods(parents, own, abstract, sameSignature, report) {
5
5
  for (const required of requirements.values()) {
6
6
  const local = own.get(required.name);
7
7
  if (local !== undefined && !sameSignature(local, required)) {
8
- report(`Method ${local.key} does not exactly preserve abstract signature ${required.key}.`);
8
+ report(`Method ${local.key} does not exactly preserve abstract signature ${required.key}.`, {
9
+ kind: 'signature',
10
+ local: local.key,
11
+ required: required.key,
12
+ });
9
13
  }
10
14
  }
11
15
  for (const local of own.values()) {
@@ -22,7 +26,10 @@ export function composeMethods(parents, own, abstract, sameSignature, report) {
22
26
  for (const inherited of frontier.values()) {
23
27
  const prior = methods.get(inherited.name);
24
28
  if (prior !== undefined && !own.has(inherited.name)) {
25
- report(`Inherited Method ${inherited.name} is ambiguous: ${prior.key}, ${inherited.key}.`);
29
+ report(`Inherited Method ${inherited.name} is ambiguous: ${prior.key}, ${inherited.key}.`, {
30
+ kind: 'ambiguous',
31
+ methods: [prior.key, inherited.key],
32
+ });
26
33
  }
27
34
  methods.set(inherited.name, inherited);
28
35
  }
@@ -34,7 +41,10 @@ export function composeMethods(parents, own, abstract, sameSignature, report) {
34
41
  const local = own.get(required.name);
35
42
  if (local === undefined && !missing.has(required.name)) {
36
43
  missing.add(required.name);
37
- report(`Concrete Class requires a local Method declaration of ${required.key}.`);
44
+ report(`Concrete Class requires a local Method declaration of ${required.key}.`, {
45
+ kind: 'missing',
46
+ required: required.key,
47
+ });
38
48
  }
39
49
  }
40
50
  }
@@ -7,9 +7,15 @@ import { evaluation, mergeAnnotations } from './types.js';
7
7
  export function evaluateInstance(compiled, input) {
8
8
  return new InstanceEvaluator(compiled).evaluate(compiled.root, input, '');
9
9
  }
10
+ /** Bound per-call retention independently of the size or diversity of an input value. */
11
+ const MAX_CACHED_PATTERN_RESULTS = 256;
12
+ const MAX_CACHED_PATTERN_INPUT_LENGTH = 128;
10
13
  class InstanceEvaluator {
11
14
  compiled;
12
15
  active = new Set();
16
+ searchResults = new Map();
17
+ fullMatchResults = new Map();
18
+ patternResultCount = 0;
13
19
  constructor(compiled) {
14
20
  this.compiled = compiled;
15
21
  }
@@ -79,14 +85,20 @@ class InstanceEvaluator {
79
85
  });
80
86
  const anyOf = schemaList(schema.anyOf);
81
87
  if (anyOf !== undefined) {
82
- const results = anyOf.map((_, index) => this.evaluate(this.child(location, 'anyOf', index), input, instancePointer));
83
- const admitted = results.filter(({ valid }) => valid);
84
- if (admitted.length === 0) {
88
+ let admitted = 0;
89
+ anyOf.forEach((_, index) => {
90
+ const branch = this.child(location, 'anyOf', index);
91
+ if (contradictsRequiredConst(branch.schema, input))
92
+ return;
93
+ const result = this.evaluate(branch, input, instancePointer);
94
+ if (!result.valid)
95
+ return;
96
+ admitted += 1;
97
+ mergeAnnotations(properties, items, result);
98
+ });
99
+ if (admitted === 0) {
85
100
  failures.push(invalid(instancePointer, 'anyOf', 'Value satisfies no anyOf branch.'));
86
101
  }
87
- else {
88
- admitted.forEach((result) => mergeAnnotations(properties, items, result));
89
- }
90
102
  }
91
103
  const oneOf = schemaList(schema.oneOf);
92
104
  if (oneOf !== undefined) {
@@ -166,11 +178,11 @@ class InstanceEvaluator {
166
178
  failures.push(invalid(instancePointer, 'minLength', 'String is below minLength.'));
167
179
  }
168
180
  }
169
- if (typeof schema.pattern === 'string' && !this.compiled.matcher(schema.pattern).test(input)) {
181
+ if (typeof schema.pattern === 'string' && !this.matchesPattern(schema.pattern, input, false)) {
170
182
  failures.push(invalid(instancePointer, 'pattern', 'String does not match pattern.'));
171
183
  }
172
184
  if (typeof schema.fullMatch === 'string' &&
173
- !this.compiled.matcher(schema.fullMatch).matches(input)) {
185
+ !this.matchesPattern(schema.fullMatch, input, true)) {
174
186
  failures.push(invalid(instancePointer, 'fullMatch', 'String does not match fullMatch.'));
175
187
  }
176
188
  }
@@ -289,9 +301,8 @@ class InstanceEvaluator {
289
301
  const patternMatched = new Set();
290
302
  let patternsValid = true;
291
303
  for (const pattern of Object.keys(patterns)) {
292
- const matcher = this.compiled.matcher(pattern);
293
304
  for (const name of names) {
294
- if (!matcher.test(name))
305
+ if (!this.matchesPattern(pattern, name, false))
295
306
  continue;
296
307
  patternMatched.add(name);
297
308
  const result = this.evaluate(this.child(location, 'patternProperties', pattern), input[name], appendPointer(instancePointer, name));
@@ -354,6 +365,51 @@ class InstanceEvaluator {
354
365
  child(parent, keyword, member) {
355
366
  return this.compiled.child(parent, keyword, member);
356
367
  }
368
+ matchesPattern(source, input, whole) {
369
+ const cacheable = input.length <= MAX_CACHED_PATTERN_INPUT_LENGTH;
370
+ const cache = whole ? this.fullMatchResults : this.searchResults;
371
+ if (cacheable) {
372
+ const found = cache.get(source)?.get(input);
373
+ if (found !== undefined)
374
+ return found;
375
+ }
376
+ const matcher = this.compiled.matcher(source);
377
+ const result = whole ? matcher.matches(input) : matcher.test(input);
378
+ if (cacheable && this.patternResultCount < MAX_CACHED_PATTERN_RESULTS) {
379
+ let values = cache.get(source);
380
+ if (values === undefined) {
381
+ values = new Map();
382
+ cache.set(source, values);
383
+ }
384
+ values.set(input, result);
385
+ this.patternResultCount++;
386
+ }
387
+ return result;
388
+ }
389
+ }
390
+ /** A required property with a different primitive const makes the entire branch impossible. */
391
+ function contradictsRequiredConst(schema, input) {
392
+ if (!isJsonObject(schema) || !isJsonObject(input) || !Array.isArray(schema.required)) {
393
+ return false;
394
+ }
395
+ if (!isJsonObject(schema.properties))
396
+ return false;
397
+ for (const key of schema.required) {
398
+ if (typeof key !== 'string' || !Object.hasOwn(input, key))
399
+ continue;
400
+ const property = schema.properties[key];
401
+ if (!isJsonObject(property) || !Object.hasOwn(property, 'const'))
402
+ continue;
403
+ const expected = property.const;
404
+ if ((expected === null ||
405
+ typeof expected === 'string' ||
406
+ typeof expected === 'number' ||
407
+ typeof expected === 'boolean') &&
408
+ input[key] !== expected) {
409
+ return true;
410
+ }
411
+ }
412
+ return false;
357
413
  }
358
414
  function schemaList(input) {
359
415
  return Array.isArray(input) ? input : undefined;
@@ -40,3 +40,24 @@ export interface DefinitionAnalysisResult {
40
40
  readonly diagnostics: readonly SchemaDiagnostic[];
41
41
  }
42
42
  export declare function analyzeDefinitions(environment: DomainResolutionContext): DefinitionAnalysisResult;
43
+ /** The exact signature required by abstract Method composition. */
44
+ export declare function sameMethodSignature(left: MethodDefinition, right: MethodDefinition): boolean;
45
+ /** Shared canonical signature used by admission and compatibility explanations. */
46
+ export declare function methodSignature(value: MethodDefinition): {
47
+ input: JsonSchema;
48
+ output: {
49
+ mode: string;
50
+ schema: JsonSchema;
51
+ item?: undefined;
52
+ } | {
53
+ schema?: undefined;
54
+ mode: string;
55
+ item: JsonSchema;
56
+ } | {
57
+ schema?: undefined;
58
+ item?: undefined;
59
+ mode: string;
60
+ };
61
+ auth: "anonymous" | "authenticated" | "authorized";
62
+ static: boolean;
63
+ };
@@ -113,20 +113,29 @@ function mergeMethods(owner, declaration, parents, diagnostics) {
113
113
  ]));
114
114
  return composeMethods(parents, own, declaration.abstract, (left, right) => sameMethodSignature(left.declaration, right.declaration), (message) => diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', message, Key.of(owner))));
115
115
  }
116
- function sameMethodSignature(left, right) {
117
- return (json.serialize(canonicalizeValueSchema(left.input, 'callable-input')) ===
118
- json.serialize(canonicalizeValueSchema(right.input, 'callable-input')) &&
119
- canonicalMethodOutput(left) === canonicalMethodOutput(right) &&
120
- left.auth === right.auth &&
121
- left.static === right.static);
116
+ /** The exact signature required by abstract Method composition. */
117
+ export function sameMethodSignature(left, right) {
118
+ return json.serialize(methodSignature(left)) === json.serialize(methodSignature(right));
119
+ }
120
+ /** Shared canonical signature used by admission and compatibility explanations. */
121
+ export function methodSignature(value) {
122
+ return {
123
+ input: canonicalizeValueSchema(value.input, 'callable-input'),
124
+ output: canonicalMethodOutput(value),
125
+ auth: value.auth,
126
+ static: value.static,
127
+ };
122
128
  }
123
129
  function canonicalMethodOutput(value) {
124
130
  switch (value.output.mode) {
125
131
  case 'value':
126
- return `value:${json.serialize(canonicalizeValueSchema(value.output.schema, 'callable-output'))}`;
132
+ return {
133
+ mode: 'value',
134
+ schema: canonicalizeValueSchema(value.output.schema, 'callable-output'),
135
+ };
127
136
  case 'stream':
128
- return `stream:${json.serialize(canonicalizeValueSchema(value.output.item, 'callable-output'))}`;
137
+ return { mode: 'stream', item: canonicalizeValueSchema(value.output.item, 'callable-output') };
129
138
  case 'binary':
130
- return 'binary';
139
+ return { mode: 'binary' };
131
140
  }
132
141
  }
@@ -0,0 +1,2 @@
1
+ export { compare, compareMeaning, compareStructure } from './compare.js';
2
+ export type { Analysis, Assessment, Comparison, ComparisonEntry, ComparisonEvidence, ComparisonRequest, ComparisonScope, Facet, Fingerprint, MeaningComparison, Observation, SchemaIdentity, StructureComparison, Subject, Usage, } from './model.js';
@@ -0,0 +1 @@
1
+ export { compare, compareMeaning, compareStructure } from './compare.js';
@@ -0,0 +1,28 @@
1
+ import type { Value } from '@astrale-os/kernel-dsl/value';
2
+ import type { CallableOutput, FunctionDefinition, MethodDefinition } from '../../../language/callable/model/callable.js';
3
+ import type { ResolvedFunction, ResolvedMethod } from '../../../language/callable/resolved.js';
4
+ import type { JsonSchema } from '../../../language/value/model/schema.js';
5
+ import type { EffectiveMethod } from '../../analysis/class.js';
6
+ import type { Fingerprint } from '../model.js';
7
+ export declare function methodExistence(method: EffectiveMethod): Value;
8
+ export declare function methodSignature(method: EffectiveMethod): Value;
9
+ export declare function methodInheritance(method: EffectiveMethod): Value;
10
+ export declare function functionSignature(declaration: FunctionDefinition): Value;
11
+ export declare function functionExistence(): Value;
12
+ /**
13
+ * Fingerprint the contract a caller compiles against, independently of its Domain revision: the
14
+ * exact key, the owner, dispatch and abstractness of a Method, and the semantic input and output
15
+ * contracts; authorization is excluded (DM-CALLABLE-FINGERPRINT). A static consumer pins this
16
+ * instead of a Domain revision, so a dependency release that leaves the callable contract untouched
17
+ * keeps every bound invocation valid.
18
+ */
19
+ export declare function callableFingerprint(callable: ResolvedFunction | ResolvedMethod): Fingerprint;
20
+ type CallableSignature = {
21
+ readonly static?: boolean;
22
+ readonly abstract?: boolean;
23
+ readonly input: JsonSchema;
24
+ readonly output: CallableOutput;
25
+ };
26
+ /** The `always` fields of CALLABLE_FIELDS: dispatch shape and the semantic input and output. */
27
+ export declare function callableSignature(declaration: Pick<FunctionDefinition, 'input' | 'output'> | Pick<MethodDefinition, 'static' | 'abstract' | 'input' | 'output'>): CallableSignature;
28
+ export {};
@@ -0,0 +1,77 @@
1
+ import { methodSignature as inheritedSignature } from '../../analysis/class.js';
2
+ import { deepFreeze } from '../../canonical/freeze.js';
3
+ import { keyForDefinitionRef } from '../coordinate.js';
4
+ import { CALLABLE_FIELDS, fieldsOf } from '../fields.js';
5
+ import { fingerprintMeaning, semanticValueSchema } from './values.js';
6
+ export function methodExistence(method) {
7
+ return deepFreeze({
8
+ coordinate: 'method',
9
+ owner: keyForDefinitionRef(method.owner),
10
+ name: method.name,
11
+ static: method.declaration.static,
12
+ abstract: method.declaration.abstract,
13
+ });
14
+ }
15
+ export function methodSignature(method) {
16
+ return deepFreeze({
17
+ coordinate: 'method',
18
+ owner: keyForDefinitionRef(method.owner),
19
+ name: method.name,
20
+ ...callableSignature(method.declaration),
21
+ });
22
+ }
23
+ export function methodInheritance(method) {
24
+ return deepFreeze({
25
+ coordinate: 'method',
26
+ owner: keyForDefinitionRef(method.owner),
27
+ name: method.name,
28
+ abstract: method.declaration.abstract,
29
+ ...inheritedSignature(method.declaration),
30
+ });
31
+ }
32
+ export function functionSignature(declaration) {
33
+ return deepFreeze({
34
+ coordinate: 'function',
35
+ ...callableSignature(declaration),
36
+ });
37
+ }
38
+ export function functionExistence() {
39
+ return Object.freeze({ coordinate: 'function' });
40
+ }
41
+ /**
42
+ * Fingerprint the contract a caller compiles against, independently of its Domain revision: the
43
+ * exact key, the owner, dispatch and abstractness of a Method, and the semantic input and output
44
+ * contracts; authorization is excluded (DM-CALLABLE-FINGERPRINT). A static consumer pins this
45
+ * instead of a Domain revision, so a dependency release that leaves the callable contract untouched
46
+ * keeps every bound invocation valid.
47
+ */
48
+ export function callableFingerprint(callable) {
49
+ // This digest input travels in every bound invocation and is recomputed by the Kernel; its
50
+ // shape is frozen, `kind` included, so a Kernel upgrade never invalidates an unchanged pin.
51
+ return fingerprintMeaning('owner' in callable
52
+ ? {
53
+ kind: 'method',
54
+ key: callable.key,
55
+ owner: callable.owner.key,
56
+ ...callableSignature(callable),
57
+ }
58
+ : { kind: 'function', key: callable.key, ...callableSignature(callable) });
59
+ }
60
+ /** The `always` fields of CALLABLE_FIELDS: dispatch shape and the semantic input and output. */
61
+ export function callableSignature(declaration) {
62
+ return {
63
+ ...fieldsOf({ declaration, roles: CALLABLE_FIELDS }),
64
+ input: semanticValueSchema(declaration.input),
65
+ output: semanticOutput(declaration.output),
66
+ };
67
+ }
68
+ function semanticOutput(output) {
69
+ switch (output.mode) {
70
+ case 'value':
71
+ return { mode: 'value', schema: semanticValueSchema(output.schema) };
72
+ case 'stream':
73
+ return { mode: 'stream', item: semanticValueSchema(output.item) };
74
+ case 'binary':
75
+ return { mode: 'binary' };
76
+ }
77
+ }
@@ -0,0 +1,28 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { Key as SemanticKey } from '../../../addressing/index.js';
3
+ import type { EffectiveField } from '../../analysis/class.js';
4
+ import type { Context } from '../context.js';
5
+ import type { ClassCoordinate } from '../coordinate.js';
6
+ import type { DependencyMeaningLevel } from '../fields.js';
7
+ import type { Report } from '../report.js';
8
+ import type { ComparisonContext, SubjectPair } from '../scope.js';
9
+ export interface ClassRequirement {
10
+ readonly ancestors: readonly SemanticKey[];
11
+ }
12
+ /**
13
+ * A reference needs the Class to exist with its kind, abstractness and orientation; inheritance
14
+ * additionally takes on the source ancestors, class policies, data contract and, for an Edge, the
15
+ * endpoint and constraint contracts. A target projected onto a source requirement keeps only the
16
+ * ancestors the source named, so a superset satisfies it while a removal changes it.
17
+ */
18
+ export declare function classMeaning(context: Context, coordinate: ClassCoordinate, level: DependencyMeaningLevel, onto?: ClassRequirement): {
19
+ readonly meaning: JsonValue;
20
+ readonly requirement: ClassRequirement;
21
+ } | undefined;
22
+ export declare function propertyMeaning(field: EffectiveField): JsonValue;
23
+ /** Newly required data is an obligation even though it was absent from the source footprint. */
24
+ export declare function compareAddedProperties({ context, pair, report, }: {
25
+ readonly context: ComparisonContext;
26
+ readonly pair: SubjectPair;
27
+ readonly report: Report;
28
+ }): void;
@@ -0,0 +1,96 @@
1
+ import { compareUnicode } from '../../../addressing/ordering.js';
2
+ import { deepFreeze } from '../../canonical/freeze.js';
3
+ import { effectiveDefinition } from '../context.js';
4
+ import { propertyCoordinate } from '../coordinate.js';
5
+ import { keyForDefinitionRef } from '../coordinate.js';
6
+ import { CLASS_FIELDS, PROPERTY_FIELDS, fieldsOf } from '../fields.js';
7
+ import { usageOf } from '../references.js';
8
+ import { semanticValueSchema } from './values.js';
9
+ /**
10
+ * A reference needs the Class to exist with its kind, abstractness and orientation; inheritance
11
+ * additionally takes on the source ancestors, class policies, data contract and, for an Edge, the
12
+ * endpoint and constraint contracts. A target projected onto a source requirement keeps only the
13
+ * ancestors the source named, so a superset satisfies it while a removal changes it.
14
+ */
15
+ export function classMeaning(context, coordinate, level, onto) {
16
+ const owner = context.schemas.get(coordinate.ref.origin);
17
+ const declaration = owner?.classes[coordinate.ref.name];
18
+ const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
19
+ if (owner === undefined || declaration === undefined || analysis === undefined)
20
+ return undefined;
21
+ const ancestors = ancestorKeys(context, coordinate.ref);
22
+ const requirement = Object.freeze({
23
+ ancestors,
24
+ });
25
+ const meaning = {
26
+ coordinate: 'class',
27
+ level: level,
28
+ ...fieldsOf({ declaration, roles: CLASS_FIELDS, level }),
29
+ ...(level === 'inherit'
30
+ ? {
31
+ extends: onto === undefined
32
+ ? ancestors
33
+ : onto.ancestors.filter((ancestor) => ancestors.includes(ancestor)),
34
+ policies: Object.fromEntries(Object.entries(declaration.policies)
35
+ .filter(([, ref]) => ref !== undefined)
36
+ .map(([role, ref]) => [role, keyForDefinitionRef(ref)])),
37
+ ...(declaration.kind === 'node'
38
+ ? {
39
+ data: analysis.data === undefined
40
+ ? null
41
+ : {
42
+ owner: keyForDefinitionRef(analysis.data.owner),
43
+ declaration: analysis.data.declaration,
44
+ },
45
+ }
46
+ : { endpoints: declaration.endpoints, constraints: declaration.constraints }),
47
+ }
48
+ : {}),
49
+ };
50
+ return { meaning: deepFreeze(meaning), requirement };
51
+ }
52
+ export function propertyMeaning(field) {
53
+ return deepFreeze({
54
+ coordinate: 'property',
55
+ owner: keyForDefinitionRef(field.owner),
56
+ name: field.name,
57
+ ...fieldsOf({ declaration: field, roles: PROPERTY_FIELDS }),
58
+ schema: semanticValueSchema(field.schema),
59
+ });
60
+ }
61
+ function ancestorKeys(context, ref) {
62
+ const seen = new Set();
63
+ const visit = (current) => {
64
+ const declaration = context.schemas.get(current.origin)?.classes[current.name];
65
+ if (declaration === undefined)
66
+ return;
67
+ for (const parent of declaration.extends) {
68
+ const key = keyForDefinitionRef(parent);
69
+ if (seen.has(key))
70
+ continue;
71
+ seen.add(key);
72
+ visit(parent);
73
+ }
74
+ };
75
+ visit(ref);
76
+ return [...seen].sort(compareUnicode);
77
+ }
78
+ /** Newly required data is an obligation even though it was absent from the source footprint. */
79
+ export function compareAddedProperties({ context, pair, report, }) {
80
+ if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
81
+ return;
82
+ for (const field of pair.target.analysis.properties.values()) {
83
+ if (pair.source.analysis.properties.has(field.identity) ||
84
+ !field.required ||
85
+ field.auto !== undefined)
86
+ continue;
87
+ const coordinate = propertyCoordinate(field.owner, field.name);
88
+ report.note({ coordinate, usage: usageOf(pair.source.schema, 'member', pair.coordinate.key) });
89
+ report.record({
90
+ pair: context.pair(coordinate),
91
+ facet: 'property.required',
92
+ after: propertyMeaning(field),
93
+ requirement: { kind: 'property.required', satisfied: false },
94
+ });
95
+ }
96
+ }
@@ -0,0 +1,12 @@
1
+ import type { Value } from '@astrale-os/kernel-dsl/value';
2
+ import type { Facet } from '../model.js';
3
+ import type { ComparisonContext, SubjectPair } from '../scope.js';
4
+ /** Shared facet comparisons know declarations, never installation or an analysis mode. */
5
+ export declare function compareFacet({ context, pair, facet, }: {
6
+ readonly context: ComparisonContext;
7
+ readonly pair: SubjectPair;
8
+ readonly facet: Exclude<Facet, 'class.composition' | 'property.required'>;
9
+ }): {
10
+ readonly before?: Value;
11
+ readonly after?: Value;
12
+ };
@@ -0,0 +1,41 @@
1
+ import { methodInheritance, functionSignature, functionExistence, methodSignature, methodExistence, } from './callables.js';
2
+ import { classMeaning, propertyMeaning } from './classes.js';
3
+ import { memberMeaning } from './members.js';
4
+ /** Shared facet comparisons know declarations, never installation or an analysis mode. */
5
+ export function compareFacet({ context, pair, facet, }) {
6
+ if (facet === 'class.reference' || facet === 'class.inherit') {
7
+ if (pair.coordinate.kind !== 'class')
8
+ throw new TypeError('Class facet requires a Class coordinate.');
9
+ const level = facet === 'class.inherit' ? 'inherit' : 'reference';
10
+ const before = classMeaning(context.before, pair.coordinate, level);
11
+ const after = classMeaning(context.after, pair.coordinate, level, before?.requirement);
12
+ return { before: before?.meaning, after: after?.meaning };
13
+ }
14
+ const value = (subject) => {
15
+ if (subject === undefined)
16
+ return undefined;
17
+ switch (facet) {
18
+ case 'property':
19
+ return subject.kind === 'property' ? propertyMeaning(subject.field) : undefined;
20
+ case 'method.existence':
21
+ return subject.kind === 'method' ? methodExistence(subject.method) : undefined;
22
+ case 'method.signature':
23
+ return subject.kind === 'method' ? methodSignature(subject.method) : undefined;
24
+ case 'method.inheritance':
25
+ return subject.kind === 'method' ? methodInheritance(subject.method) : undefined;
26
+ case 'function.existence':
27
+ return functionExistence();
28
+ case 'function.signature':
29
+ return subject.kind === 'member'
30
+ ? functionSignature(subject.schema.functions[subject.coordinate.ref.name])
31
+ : undefined;
32
+ case 'policy':
33
+ case 'view':
34
+ case 'core':
35
+ return subject.kind === 'member'
36
+ ? memberMeaning(subject.schema, subject.coordinate.ref)
37
+ : undefined;
38
+ }
39
+ };
40
+ return { before: value(pair.source), after: value(pair.target) };
41
+ }
@@ -0,0 +1,4 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { DomainSchema } from '../../model/schema.js';
3
+ import type { MemberRef } from '../coordinate.js';
4
+ export declare function memberMeaning(owner: DomainSchema, ref: MemberRef): JsonValue | undefined;
@@ -0,0 +1,49 @@
1
+ import { deepFreeze } from '../../canonical/freeze.js';
2
+ import { CORE_FIELDS, POLICY_FIELDS, VIEW_FIELDS, fieldsOf } from '../fields.js';
3
+ import { functionSignature } from './callables.js';
4
+ export function memberMeaning(owner, ref) {
5
+ const meaning = memberMeaningOf(owner, ref);
6
+ return meaning === undefined ? undefined : deepFreeze(meaning);
7
+ }
8
+ function memberMeaningOf(owner, ref) {
9
+ if (ref.origin !== owner.origin)
10
+ return undefined;
11
+ switch (ref.kind) {
12
+ case 'function': {
13
+ const declaration = owner.functions[ref.name];
14
+ return declaration === undefined ? undefined : functionSignature(declaration);
15
+ }
16
+ case 'policy': {
17
+ const declaration = owner.policies[ref.name];
18
+ return declaration === undefined
19
+ ? undefined
20
+ : {
21
+ coordinate: 'policy',
22
+ ...fieldsOf({ declaration, roles: POLICY_FIELDS }),
23
+ };
24
+ }
25
+ case 'view': {
26
+ const declaration = owner.views[ref.name];
27
+ return declaration === undefined
28
+ ? undefined
29
+ : {
30
+ coordinate: 'view',
31
+ ...fieldsOf({ declaration, roles: VIEW_FIELDS }),
32
+ };
33
+ }
34
+ case 'core': {
35
+ const node = owner.core.nodes[ref.name];
36
+ return node === undefined
37
+ ? undefined
38
+ : {
39
+ coordinate: 'core',
40
+ ...fieldsOf({ declaration: node, roles: CORE_FIELDS }),
41
+ edges: incidentCoreEdges(owner, ref.name).map((edge) => fieldsOf({ declaration: edge, roles: CORE_FIELDS })),
42
+ };
43
+ }
44
+ }
45
+ }
46
+ function incidentCoreEdges(owner, name) {
47
+ return owner.core.edges.filter((edge) => (edge.source.kind === 'core' && edge.source.name === name) ||
48
+ (edge.target.kind === 'core' && edge.target.name === name));
49
+ }
@@ -0,0 +1,5 @@
1
+ import type { JsonSchema } from '../../../language/value/model/schema.js';
2
+ import type { Fingerprint } from '../model.js';
3
+ /** Drop annotation keywords wherever a schema keyword nests a schema; never inside data. */
4
+ export declare function semanticValueSchema(value: JsonSchema): JsonSchema;
5
+ export declare function fingerprintMeaning(value: unknown): Fingerprint;
@@ -0,0 +1,37 @@
1
+ import { json } from '@astrale-os/kernel-dsl/value';
2
+ import { isValueObject as isJsonObject } from '../../../language/value/model/object.js';
3
+ import { SCHEMA_ARRAY_KEYWORDS, SCHEMA_MAP_KEYWORDS, SCHEMA_SINGLE_KEYWORDS, } from '../../../language/value/syntax/keywords.js';
4
+ import { digestDomainSchemaBytes } from '../../canonical/domain.digest.js';
5
+ import { ANNOTATION_KEYWORDS } from '../fields.js';
6
+ /** Drop annotation keywords wherever a schema keyword nests a schema; never inside data. */
7
+ export function semanticValueSchema(value) {
8
+ if (typeof value === 'boolean')
9
+ return value;
10
+ const semantic = {};
11
+ for (const [keyword, entry] of Object.entries(value)) {
12
+ if (ANNOTATION_KEYWORDS.includes(keyword))
13
+ continue;
14
+ if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(entry)) {
15
+ semantic[keyword] = semanticValueSchema(entry);
16
+ }
17
+ else if (SCHEMA_ARRAY_KEYWORDS.has(keyword) && Array.isArray(entry)) {
18
+ semantic[keyword] = entry.map((item) => isJsonSchema(item) ? semanticValueSchema(item) : item);
19
+ }
20
+ else if (SCHEMA_MAP_KEYWORDS.has(keyword) && isJsonObject(entry)) {
21
+ semantic[keyword] = Object.fromEntries(Object.entries(entry).map(([name, item]) => [
22
+ name,
23
+ isJsonSchema(item) ? semanticValueSchema(item) : item,
24
+ ]));
25
+ }
26
+ else {
27
+ semantic[keyword] = entry;
28
+ }
29
+ }
30
+ return semantic;
31
+ }
32
+ export function fingerprintMeaning(value) {
33
+ return digestDomainSchemaBytes(json.encode(value));
34
+ }
35
+ function isJsonSchema(value) {
36
+ return typeof value === 'boolean' || isJsonObject(value);
37
+ }