@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.
- package/dist/v1/language/callable/composition.d.ts +13 -1
- package/dist/v1/language/callable/composition.js +13 -3
- package/dist/v1/language/value/evaluate/evaluator.js +66 -10
- package/dist/v1/schema/analysis/class.d.ts +21 -0
- package/dist/v1/schema/analysis/class.js +18 -9
- package/dist/v1/schema/compatibility/api.d.ts +2 -0
- package/dist/v1/schema/compatibility/api.js +1 -0
- package/dist/v1/schema/compatibility/comparators/callables.d.ts +28 -0
- package/dist/v1/schema/compatibility/comparators/callables.js +77 -0
- package/dist/v1/schema/compatibility/comparators/classes.d.ts +28 -0
- package/dist/v1/schema/compatibility/comparators/classes.js +96 -0
- package/dist/v1/schema/compatibility/comparators/index.d.ts +12 -0
- package/dist/v1/schema/compatibility/comparators/index.js +41 -0
- package/dist/v1/schema/compatibility/comparators/members.d.ts +4 -0
- package/dist/v1/schema/compatibility/comparators/members.js +49 -0
- package/dist/v1/schema/compatibility/comparators/values.d.ts +5 -0
- package/dist/v1/schema/compatibility/comparators/values.js +37 -0
- package/dist/v1/schema/compatibility/compare.d.ts +5 -57
- package/dist/v1/schema/compatibility/compare.js +19 -162
- package/dist/v1/schema/compatibility/coordinate.d.ts +2 -7
- package/dist/v1/schema/compatibility/coordinate.js +2 -6
- package/dist/v1/schema/compatibility/evidence.d.ts +5 -0
- package/dist/v1/schema/compatibility/evidence.js +51 -0
- package/dist/v1/schema/compatibility/fields.d.ts +6 -0
- package/dist/v1/schema/compatibility/fields.js +5 -0
- package/dist/v1/schema/compatibility/index.d.ts +5 -4
- package/dist/v1/schema/compatibility/index.js +3 -2
- package/dist/v1/schema/compatibility/legacy/index.d.ts +58 -0
- package/dist/v1/schema/compatibility/legacy/index.js +145 -0
- package/dist/v1/schema/compatibility/meaning/footprint.d.ts +7 -0
- package/dist/v1/schema/compatibility/meaning/footprint.js +57 -0
- package/dist/v1/schema/compatibility/meaning/index.d.ts +3 -0
- package/dist/v1/schema/compatibility/meaning/index.js +14 -0
- package/dist/v1/schema/compatibility/model.d.ts +96 -0
- package/dist/v1/schema/compatibility/model.js +1 -0
- package/dist/v1/schema/compatibility/references.d.ts +30 -0
- package/dist/v1/schema/compatibility/references.js +122 -0
- package/dist/v1/schema/compatibility/report.d.ts +26 -0
- package/dist/v1/schema/compatibility/report.js +100 -0
- package/dist/v1/schema/compatibility/scope.d.ts +27 -0
- package/dist/v1/schema/compatibility/scope.js +78 -0
- package/dist/v1/schema/compatibility/structure/index.d.ts +3 -0
- package/dist/v1/schema/compatibility/structure/index.js +16 -0
- package/dist/v1/schema/compatibility/structure/inheritance.d.ts +7 -0
- package/dist/v1/schema/compatibility/structure/inheritance.js +68 -0
- package/dist/v1/schema/compatibility/structure/requirements.d.ts +7 -0
- package/dist/v1/schema/compatibility/structure/requirements.js +70 -0
- package/dist/v1/schema/compatibility/subjects.d.ts +34 -0
- package/dist/v1/schema/compatibility/subjects.js +86 -0
- package/dist/v1/schema/fingerprint.d.ts +2 -2
- package/dist/v1/schema/fingerprint.js +1 -1
- package/dist/v1/schema/index.d.ts +3 -0
- package/dist/v1/schema/index.js +2 -0
- package/package.json +2 -2
- package/dist/v1/schema/compatibility/footprint.d.ts +0 -25
- package/dist/v1/schema/compatibility/footprint.js +0 -264
- package/dist/v1/schema/compatibility/meaning.d.ts +0 -44
- 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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
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.
|
|
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 (!
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
132
|
+
return {
|
|
133
|
+
mode: 'value',
|
|
134
|
+
schema: canonicalizeValueSchema(value.output.schema, 'callable-output'),
|
|
135
|
+
};
|
|
127
136
|
case 'stream':
|
|
128
|
-
return
|
|
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
|
+
}
|