@astrale-os/kernel-dsl 0.2.0-beta.33 → 0.2.0-beta.35
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/value/regexp/matcher.d.ts +4 -1
- package/dist/v1/language/value/regexp/matcher.js +114 -29
- package/dist/v1/schema/compatibility/meaning.d.ts +30 -6
- package/dist/v1/schema/compatibility/meaning.js +306 -249
- package/dist/v1/schema/index.d.ts +2 -2
- package/dist/v1/schema/index.js +1 -1
- package/dist/v1/schema/resolution/origins.d.ts +17 -4
- package/dist/v1/schema/resolution/origins.js +37 -26
- package/package.json +2 -2
|
@@ -3,5 +3,8 @@ export interface IRegexpMatcher {
|
|
|
3
3
|
test(value: string): boolean;
|
|
4
4
|
matches(value: string): boolean;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Load the portable Thompson NFA executor shared by search and whole-string matching.
|
|
8
|
+
* Scratch belongs to one invocation: no match-result cache, shared mutable cursor or DFA expansion.
|
|
9
|
+
*/
|
|
7
10
|
export declare function loadPattern(pattern: Pattern): IRegexpMatcher;
|
|
@@ -1,50 +1,135 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Load the portable Thompson NFA executor shared by search and whole-string matching.
|
|
3
|
+
* Scratch belongs to one invocation: no match-result cache, shared mutable cursor or DFA expansion.
|
|
4
|
+
*/
|
|
2
5
|
export function loadPattern(pattern) {
|
|
3
|
-
const
|
|
6
|
+
const states = pattern.states;
|
|
7
|
+
const start = initialFrontier(pattern);
|
|
4
8
|
const run = (input, search) => {
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
if (input.length === 0)
|
|
10
|
+
return start.accepting;
|
|
11
|
+
if (search && start.accepting)
|
|
7
12
|
return true;
|
|
13
|
+
const resolve = executionStates(states);
|
|
14
|
+
// Initial frontier entries consume characters, so none can be a dead conduit cycle.
|
|
15
|
+
const initial = start.frontier.map((index) => resolve(index));
|
|
16
|
+
let active = [...initial];
|
|
17
|
+
let next = [];
|
|
18
|
+
const pending = [];
|
|
19
|
+
let activeCount = start.frontier.length;
|
|
20
|
+
let nextCount = 0;
|
|
21
|
+
let epoch = 0;
|
|
22
|
+
let accepting = false;
|
|
23
|
+
function add(destination) {
|
|
24
|
+
const seed = resolve(destination);
|
|
25
|
+
if (seed === null || seed.epoch === epoch)
|
|
26
|
+
return;
|
|
27
|
+
let count = 1;
|
|
28
|
+
pending[0] = seed;
|
|
29
|
+
seed.epoch = epoch;
|
|
30
|
+
while (count > 0) {
|
|
31
|
+
const entry = pending[--count];
|
|
32
|
+
const state = entry.state;
|
|
33
|
+
if (state.accepting === true)
|
|
34
|
+
accepting = true;
|
|
35
|
+
if (state.transitions.length > 0)
|
|
36
|
+
next[nextCount++] = entry;
|
|
37
|
+
for (const destination of state.epsilon) {
|
|
38
|
+
const target = resolve(destination);
|
|
39
|
+
if (target === null || target.epoch === epoch)
|
|
40
|
+
continue;
|
|
41
|
+
target.epoch = epoch;
|
|
42
|
+
pending[count++] = target;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
8
46
|
for (const character of input) {
|
|
47
|
+
if (!search && activeCount === 0)
|
|
48
|
+
return false;
|
|
49
|
+
epoch++;
|
|
50
|
+
nextCount = 0;
|
|
51
|
+
accepting = false;
|
|
9
52
|
const scalar = character.codePointAt(0);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
53
|
+
for (let index = 0; index < activeCount; index++) {
|
|
54
|
+
for (const transition of active[index].state.transitions) {
|
|
55
|
+
for (const [minimum, maximum] of transition.ranges) {
|
|
56
|
+
if (scalar < minimum || scalar > maximum)
|
|
57
|
+
continue;
|
|
58
|
+
add(transition.to);
|
|
59
|
+
break;
|
|
15
60
|
}
|
|
16
61
|
}
|
|
17
62
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
63
|
+
if (search) {
|
|
64
|
+
if (accepting)
|
|
65
|
+
return true;
|
|
66
|
+
// The complete initial epsilon closure is already known; do not traverse it again.
|
|
67
|
+
for (const entry of initial) {
|
|
68
|
+
if (entry.epoch === epoch)
|
|
69
|
+
continue;
|
|
70
|
+
entry.epoch = epoch;
|
|
71
|
+
next[nextCount++] = entry;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const previous = active;
|
|
75
|
+
active = next;
|
|
76
|
+
next = previous;
|
|
77
|
+
activeCount = nextCount;
|
|
24
78
|
}
|
|
25
|
-
return
|
|
79
|
+
return accepting;
|
|
26
80
|
};
|
|
27
81
|
return Object.freeze({
|
|
28
82
|
test: (input) => run(input, true),
|
|
29
83
|
matches: (input) => run(input, false),
|
|
30
84
|
});
|
|
31
85
|
}
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
const
|
|
86
|
+
function initialFrontier(pattern) {
|
|
87
|
+
const start = pattern.start;
|
|
88
|
+
const seen = new Set([start]);
|
|
89
|
+
const pending = [start];
|
|
90
|
+
const frontier = [];
|
|
91
|
+
let accepting = false;
|
|
35
92
|
while (pending.length > 0) {
|
|
36
|
-
|
|
37
|
-
|
|
93
|
+
const index = pending.pop();
|
|
94
|
+
const state = pattern.states[index];
|
|
95
|
+
if (state.accepting === true)
|
|
96
|
+
accepting = true;
|
|
97
|
+
if (state.transitions.length > 0)
|
|
98
|
+
frontier.push(index);
|
|
99
|
+
for (const target of state.epsilon) {
|
|
100
|
+
if (seen.has(target))
|
|
38
101
|
continue;
|
|
39
|
-
|
|
40
|
-
pending.push(
|
|
102
|
+
seen.add(target);
|
|
103
|
+
pending.push(target);
|
|
41
104
|
}
|
|
42
105
|
}
|
|
43
|
-
return
|
|
106
|
+
return { frontier: Object.freeze(frontier), accepting };
|
|
44
107
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
108
|
+
/** Resolve only visited conduits, once within this invocation; never retain cross-call state. */
|
|
109
|
+
function executionStates(states) {
|
|
110
|
+
const destinations = new Map();
|
|
111
|
+
return (index) => {
|
|
112
|
+
const known = destinations.get(index);
|
|
113
|
+
if (known !== undefined)
|
|
114
|
+
return known;
|
|
115
|
+
const path = [];
|
|
116
|
+
let current = index;
|
|
117
|
+
let destination = destinations.get(current);
|
|
118
|
+
while (destination === undefined) {
|
|
119
|
+
const state = states[current];
|
|
120
|
+
if (state.accepting === true || state.transitions.length > 0 || state.epsilon.length !== 1) {
|
|
121
|
+
destination = { state, epoch: 0 };
|
|
122
|
+
destinations.set(current, destination);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
// A revisit before resolving the path is a dead, epsilon-only cycle.
|
|
126
|
+
destinations.set(current, null);
|
|
127
|
+
path.push(current);
|
|
128
|
+
current = state.epsilon[0];
|
|
129
|
+
destination = destinations.get(current);
|
|
130
|
+
}
|
|
131
|
+
for (const member of path)
|
|
132
|
+
destinations.set(member, destination);
|
|
133
|
+
return destination;
|
|
134
|
+
};
|
|
50
135
|
}
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
import type { Key as SemanticKey } from '../../addressing/index.js';
|
|
2
2
|
import type { Revision } from '../../addressing/index.js';
|
|
3
|
+
import type { ResolvedFunction, ResolvedMethod } from '../../language/callable/resolved.js';
|
|
3
4
|
import type { DomainSchema } from '../model/schema.js';
|
|
4
5
|
import { Key } from '../../addressing/index.js';
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* How strongly a dependent relies on a dependency Class. `inherit` covers `extends` and Core
|
|
8
|
+
* instantiation, which take on the Class creation contract; `reference` covers every other site,
|
|
9
|
+
* which only names the Class and reads its surface.
|
|
10
|
+
*/
|
|
11
|
+
export type DependencyMeaningLevel = 'reference' | 'inherit';
|
|
12
|
+
export type DependencyMeaningChangeKind = 'missing' | 'changed' | 'added';
|
|
6
13
|
export type MeaningFingerprint = `sha256:${string}`;
|
|
7
|
-
export
|
|
14
|
+
export type DependencyMeaningChange = {
|
|
8
15
|
readonly key: SemanticKey;
|
|
9
|
-
readonly kind:
|
|
16
|
+
readonly kind: 'missing';
|
|
10
17
|
readonly sourceFingerprint: MeaningFingerprint;
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
} | {
|
|
19
|
+
readonly key: SemanticKey;
|
|
20
|
+
readonly kind: 'changed';
|
|
21
|
+
readonly sourceFingerprint: MeaningFingerprint;
|
|
22
|
+
readonly targetFingerprint: MeaningFingerprint;
|
|
23
|
+
} | {
|
|
24
|
+
/** A required, non-automatic Property the target adds to an inherited Class. */
|
|
25
|
+
readonly key: SemanticKey;
|
|
26
|
+
readonly kind: 'added';
|
|
27
|
+
readonly targetFingerprint: MeaningFingerprint;
|
|
28
|
+
};
|
|
13
29
|
export interface DependencyMeaningComparison {
|
|
14
|
-
readonly version:
|
|
30
|
+
readonly version: 2;
|
|
15
31
|
readonly dependency: Key.Origin;
|
|
16
32
|
readonly source: Revision;
|
|
17
33
|
readonly target: Revision;
|
|
@@ -27,3 +43,11 @@ export interface DependencyMeaningComparison {
|
|
|
27
43
|
export declare function compareDependencyMeaning(dependent: DomainSchema, target: DomainSchema): DependencyMeaningComparison;
|
|
28
44
|
/** Compare one explicit non-empty source member surface and its reachable V1 meaning. */
|
|
29
45
|
export declare function compareMemberMeaning(source: DomainSchema, target: DomainSchema, roots: readonly [SemanticKey, ...SemanticKey[]]): DependencyMeaningComparison;
|
|
46
|
+
/**
|
|
47
|
+
* Fingerprint the contract a caller compiles against, independently of its Domain revision: the
|
|
48
|
+
* exact key, the owner, dispatch and abstractness of a Method, and the semantic input and output
|
|
49
|
+
* contracts; authorization is excluded (DM-CALLABLE-FINGERPRINT). A static consumer pins this
|
|
50
|
+
* instead of a Domain revision, so a dependency release that leaves the callable contract untouched
|
|
51
|
+
* keeps every bound invocation valid.
|
|
52
|
+
*/
|
|
53
|
+
export declare function callableFingerprint(callable: ResolvedFunction | ResolvedMethod): MeaningFingerprint;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { Key, MethodKey, PropertyKey } from '../../addressing/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { compareUnicode, definitionRefIndex } 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';
|
|
@@ -8,7 +8,7 @@ import { revisionOfDomainSchema } from '../canonical/revision.js';
|
|
|
8
8
|
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
9
9
|
import { knownDomainClosure, lookupForDomainClosure } from '../resolution/closure.js';
|
|
10
10
|
import { resolve } from '../resolution/domain.js';
|
|
11
|
-
import { collectCallableRefs, collectClassRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, } from '../resolution/origins.js';
|
|
11
|
+
import { collectCallableContractRefs, collectCallableRefs, collectClassPolicyRefs, collectClassRefs, collectCoreRefs, collectEndpointRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, } from '../resolution/origins.js';
|
|
12
12
|
import { validateDomainSchemaSemantics } from '../validation/domain.js';
|
|
13
13
|
const VALUE_SCHEMA_ANNOTATIONS = new Set([
|
|
14
14
|
'$comment',
|
|
@@ -31,19 +31,15 @@ const VALUE_SCHEMA_ANNOTATIONS = new Set([
|
|
|
31
31
|
*/
|
|
32
32
|
export function compareDependencyMeaning(dependent, target) {
|
|
33
33
|
resolve(dependent);
|
|
34
|
-
const
|
|
35
|
-
const source =
|
|
34
|
+
const context = contextOf(dependent);
|
|
35
|
+
const source = (knownDomainClosure(dependent) ?? []).find((candidate) => candidate.schema.origin === target.origin)?.schema;
|
|
36
36
|
if (source === undefined) {
|
|
37
37
|
throw new SchemaError([
|
|
38
38
|
diagnostic('DM_DEPENDENCY_NOT_RETAINED', '/target/origin', `Domain ${dependent.origin} was not admitted with a dependency at ${target.origin}.`),
|
|
39
39
|
]);
|
|
40
40
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
...closure.map(({ schema: candidate }) => [candidate.origin, candidate]),
|
|
44
|
-
]);
|
|
45
|
-
const sourceMeanings = collectDependencyMeanings(dependent, source.origin, schemas);
|
|
46
|
-
return compareSourceMeanings(source, target, sourceMeanings);
|
|
41
|
+
const meanings = collectReachableMeanings(collectSchemaRoots(dependent), source.origin, context);
|
|
42
|
+
return compareSourceMeanings(source, target, meanings);
|
|
47
43
|
}
|
|
48
44
|
/** Compare one explicit non-empty source member surface and its reachable V1 meaning. */
|
|
49
45
|
export function compareMemberMeaning(source, target, roots) {
|
|
@@ -58,24 +54,55 @@ export function compareMemberMeaning(source, target, roots) {
|
|
|
58
54
|
]);
|
|
59
55
|
}
|
|
60
56
|
resolve(source);
|
|
61
|
-
const
|
|
57
|
+
const context = contextOf(source);
|
|
58
|
+
const coordinates = roots.map((key, index) => coordinateForKey(context, source, key, index));
|
|
59
|
+
const meanings = collectReachableMeanings(coordinates, source.origin, context);
|
|
60
|
+
return compareSourceMeanings(source, target, meanings);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Fingerprint the contract a caller compiles against, independently of its Domain revision: the
|
|
64
|
+
* exact key, the owner, dispatch and abstractness of a Method, and the semantic input and output
|
|
65
|
+
* contracts; authorization is excluded (DM-CALLABLE-FINGERPRINT). A static consumer pins this
|
|
66
|
+
* instead of a Domain revision, so a dependency release that leaves the callable contract untouched
|
|
67
|
+
* keeps every bound invocation valid.
|
|
68
|
+
*/
|
|
69
|
+
export function callableFingerprint(callable) {
|
|
70
|
+
return fingerprintMeaning('owner' in callable
|
|
71
|
+
? {
|
|
72
|
+
kind: 'method',
|
|
73
|
+
key: callable.key,
|
|
74
|
+
owner: callable.owner.key,
|
|
75
|
+
...semanticMethod(callable),
|
|
76
|
+
}
|
|
77
|
+
: { kind: 'function', key: callable.key, ...semanticFunction(callable) });
|
|
78
|
+
}
|
|
79
|
+
function contextOf(root) {
|
|
62
80
|
const schemas = new Map([
|
|
63
|
-
[
|
|
64
|
-
...
|
|
81
|
+
[root.origin, root],
|
|
82
|
+
...(knownDomainClosure(root) ?? []).map(({ schema }) => [schema.origin, schema]),
|
|
65
83
|
]);
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
const analyses = new Map();
|
|
85
|
+
return {
|
|
86
|
+
schemas,
|
|
87
|
+
analysis(owner) {
|
|
88
|
+
if (!analyses.has(owner)) {
|
|
89
|
+
const validation = validateDomainSchemaSemantics(owner, lookupForDomainClosure(owner));
|
|
90
|
+
analyses.set(owner, validation.diagnostics.length > 0 ? undefined : validation.analysis.definitions);
|
|
91
|
+
}
|
|
92
|
+
return analyses.get(owner);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
69
95
|
}
|
|
70
96
|
function compareSourceMeanings(source, target, sourceMeanings) {
|
|
71
97
|
// Prove the candidate still has an exact accepted semantic context before comparing it.
|
|
72
98
|
resolve(target);
|
|
99
|
+
const context = contextOf(target);
|
|
73
100
|
const footprint = Object.freeze([...sourceMeanings.keys()].sort(compareUnicode));
|
|
74
101
|
const changes = [];
|
|
75
102
|
const comparisonCoordinates = [];
|
|
76
103
|
for (const key of footprint) {
|
|
77
104
|
const sourceMeaning = sourceMeanings.get(key);
|
|
78
|
-
const targetMeaning = meaningForCoordinate(
|
|
105
|
+
const targetMeaning = meaningForCoordinate(context, sourceMeaning.coordinate, sourceMeaning.requirement);
|
|
79
106
|
const targetFingerprint = targetMeaning === undefined ? undefined : fingerprintMeaning(targetMeaning);
|
|
80
107
|
comparisonCoordinates.push([
|
|
81
108
|
key,
|
|
@@ -83,11 +110,7 @@ function compareSourceMeanings(source, target, sourceMeanings) {
|
|
|
83
110
|
targetFingerprint ?? null,
|
|
84
111
|
]);
|
|
85
112
|
if (targetFingerprint === undefined) {
|
|
86
|
-
changes.push(Object.freeze({
|
|
87
|
-
key,
|
|
88
|
-
kind: 'missing',
|
|
89
|
-
sourceFingerprint: sourceMeaning.fingerprint,
|
|
90
|
-
}));
|
|
113
|
+
changes.push(Object.freeze({ key, kind: 'missing', sourceFingerprint: sourceMeaning.fingerprint }));
|
|
91
114
|
}
|
|
92
115
|
else if (targetFingerprint !== sourceMeaning.fingerprint) {
|
|
93
116
|
changes.push(Object.freeze({
|
|
@@ -98,177 +121,214 @@ function compareSourceMeanings(source, target, sourceMeanings) {
|
|
|
98
121
|
}));
|
|
99
122
|
}
|
|
100
123
|
}
|
|
124
|
+
const additions = addedObligations(context, sourceMeanings);
|
|
101
125
|
const sourceRevision = revisionOfDomainSchema(source);
|
|
102
126
|
const targetRevision = revisionOfDomainSchema(target);
|
|
103
|
-
const immutableChanges = Object.freeze(changes);
|
|
104
127
|
const fingerprint = fingerprintMeaning({
|
|
105
|
-
version:
|
|
128
|
+
version: 2,
|
|
106
129
|
dependency: source.origin,
|
|
107
130
|
source: sourceRevision,
|
|
108
131
|
target: targetRevision,
|
|
109
132
|
coordinates: comparisonCoordinates,
|
|
133
|
+
additions: additions.map(({ key, targetFingerprint }) => [key, targetFingerprint]),
|
|
110
134
|
});
|
|
111
135
|
return Object.freeze({
|
|
112
|
-
version:
|
|
136
|
+
version: 2,
|
|
113
137
|
dependency: source.origin,
|
|
114
138
|
source: sourceRevision,
|
|
115
139
|
target: targetRevision,
|
|
116
140
|
footprint,
|
|
117
|
-
changes:
|
|
141
|
+
changes: Object.freeze([...changes, ...additions]),
|
|
118
142
|
fingerprint,
|
|
119
143
|
});
|
|
120
144
|
}
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
/** A target may extend an inherited Class only with optional or automatic Properties. */
|
|
146
|
+
function addedObligations(context, sourceMeanings) {
|
|
147
|
+
const additions = new Map();
|
|
148
|
+
for (const { coordinate, requirement } of sourceMeanings.values()) {
|
|
149
|
+
if (coordinate.kind !== 'class' || coordinate.level !== 'inherit' || requirement === undefined)
|
|
150
|
+
continue;
|
|
151
|
+
const owner = context.schemas.get(coordinate.ref.origin);
|
|
152
|
+
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
|
|
153
|
+
if (analysis === undefined)
|
|
154
|
+
continue;
|
|
155
|
+
for (const field of analysis.properties.values()) {
|
|
156
|
+
if (requirement.properties.has(field.identity) || !field.required || field.auto !== undefined)
|
|
157
|
+
continue;
|
|
158
|
+
additions.set(field.identity, Object.freeze({
|
|
159
|
+
key: field.identity,
|
|
160
|
+
kind: 'added',
|
|
161
|
+
targetFingerprint: fingerprintMeaning(propertyMeaning(field)),
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return [...additions.values()].sort((left, right) => compareUnicode(left.key, right.key));
|
|
166
|
+
}
|
|
167
|
+
function collectSchemaRoots(owner) {
|
|
168
|
+
const classes = new Map();
|
|
169
|
+
const members = new Map();
|
|
170
|
+
const add = (ref, site) => {
|
|
171
|
+
if (ref.kind === 'class') {
|
|
172
|
+
const index = definitionRefIndex(ref);
|
|
173
|
+
const level = levelOf(site);
|
|
174
|
+
const previous = classes.get(index);
|
|
175
|
+
if (previous === undefined || (previous.level === 'reference' && level === 'inherit')) {
|
|
176
|
+
classes.set(index, classCoordinate(ref, level));
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
members.set(definitionRefIndex(ref), memberCoordinate(ref));
|
|
181
|
+
};
|
|
182
|
+
for (const declaration of Object.values(owner.classes))
|
|
183
|
+
collectClassRefs(declaration, add);
|
|
184
|
+
for (const declaration of Object.values(owner.functions))
|
|
185
|
+
collectCallableRefs(declaration, add);
|
|
186
|
+
for (const declaration of Object.values(owner.policies)) {
|
|
187
|
+
collectPolicyDefinitionRefs(declaration, add);
|
|
188
|
+
}
|
|
189
|
+
for (const declaration of Object.values(owner.views)) {
|
|
190
|
+
if (declaration.target.kind === 'definition') {
|
|
191
|
+
declaration.target.definitions.forEach((ref) => add(ref, 'view'));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
collectCoreRefs(owner, add);
|
|
195
|
+
return [...classes.values(), ...members.values()].sort((left, right) => compareUnicode(left.key, right.key));
|
|
123
196
|
}
|
|
124
|
-
function collectReachableMeanings(roots, selectedOrigin,
|
|
197
|
+
function collectReachableMeanings(roots, selectedOrigin, context) {
|
|
125
198
|
const meanings = new Map();
|
|
126
|
-
const
|
|
127
|
-
const
|
|
199
|
+
const classLevels = new Map();
|
|
200
|
+
const visitedMembers = new Set();
|
|
201
|
+
const pendingClasses = [];
|
|
202
|
+
const pendingMembers = [];
|
|
203
|
+
const push = (ref, site) => {
|
|
204
|
+
if (ref.kind === 'class')
|
|
205
|
+
pendingClasses.push(classCoordinate(ref, levelOf(site)));
|
|
206
|
+
else
|
|
207
|
+
pendingMembers.push(memberCoordinate(ref));
|
|
208
|
+
};
|
|
128
209
|
for (const coordinate of roots) {
|
|
210
|
+
if (coordinate.kind === 'class') {
|
|
211
|
+
pendingClasses.push(coordinate);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
129
214
|
if (coordinate.kind === 'member') {
|
|
130
|
-
|
|
215
|
+
pendingMembers.push(coordinate);
|
|
131
216
|
continue;
|
|
132
217
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
if (owner === undefined || meaning === undefined) {
|
|
218
|
+
const meaning = meaningForCoordinate(context, coordinate);
|
|
219
|
+
if (meaning === undefined) {
|
|
136
220
|
throw new SchemaError([
|
|
137
221
|
diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', `Member comparison root ${coordinate.key} does not exist in its source context.`, coordinate.key),
|
|
138
222
|
]);
|
|
139
223
|
}
|
|
140
224
|
retainMeaning(meanings, selectedOrigin, coordinate, meaning);
|
|
141
|
-
collectCoordinateSemanticRefs(
|
|
225
|
+
collectCoordinateSemanticRefs(context, coordinate, push);
|
|
142
226
|
}
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const owner =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
227
|
+
const visitClass = (coordinate) => {
|
|
228
|
+
const index = definitionRefIndex(coordinate.ref);
|
|
229
|
+
const previous = classLevels.get(index);
|
|
230
|
+
if (previous === 'inherit' || previous === coordinate.level)
|
|
231
|
+
return;
|
|
232
|
+
classLevels.set(index, coordinate.level);
|
|
233
|
+
const owner = requireOwner(context, coordinate.ref, coordinate.key);
|
|
234
|
+
const declaration = owner.classes[coordinate.ref.name];
|
|
235
|
+
const analysis = effectiveDefinition(context, owner, coordinate.ref);
|
|
236
|
+
const resolved = classMeaning(context, coordinate);
|
|
237
|
+
if (declaration === undefined || analysis === undefined || resolved === undefined) {
|
|
238
|
+
throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
|
|
154
239
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
240
|
+
retainMeaning(meanings, selectedOrigin, coordinate, resolved.meaning, resolved.requirement);
|
|
241
|
+
if (previous === undefined) {
|
|
242
|
+
// The surface is level-independent: every effective Property and Method the dependent may
|
|
243
|
+
// read or call, with the Definitions their contracts and policies name.
|
|
244
|
+
for (const field of analysis.properties.values()) {
|
|
245
|
+
retainMeaning(meanings, selectedOrigin, propertyCoordinate(field.owner, field.name), propertyMeaning(field));
|
|
246
|
+
collectValueDefinitionRefs(field.schema, push);
|
|
247
|
+
}
|
|
248
|
+
for (const method of analysis.methods.values()) {
|
|
249
|
+
retainMeaning(meanings, selectedOrigin, methodCoordinate(method.owner, method.name), methodMeaning(method));
|
|
250
|
+
collectCallableContractRefs(method.declaration, push);
|
|
251
|
+
}
|
|
252
|
+
collectClassPolicyRefs(declaration, push);
|
|
161
253
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
254
|
+
if (coordinate.level === 'inherit') {
|
|
255
|
+
// Only inheritance takes on the ancestry and, for an Edge, the endpoint contract.
|
|
256
|
+
declaration.extends.forEach((parent) => push(parent, 'extends'));
|
|
257
|
+
if (declaration.kind === 'edge')
|
|
258
|
+
collectEndpointRefs(declaration, push);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
const visitMember = (coordinate) => {
|
|
262
|
+
const index = definitionRefIndex(coordinate.ref);
|
|
263
|
+
if (visitedMembers.has(index))
|
|
264
|
+
return;
|
|
265
|
+
visitedMembers.add(index);
|
|
266
|
+
const owner = requireOwner(context, coordinate.ref, coordinate.key);
|
|
267
|
+
const meaning = memberMeaning(owner, coordinate.ref);
|
|
268
|
+
if (meaning === undefined) {
|
|
269
|
+
throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
|
|
270
|
+
}
|
|
271
|
+
retainMeaning(meanings, selectedOrigin, coordinate, meaning);
|
|
272
|
+
collectMemberSemanticRefs(owner, coordinate.ref, push);
|
|
273
|
+
};
|
|
274
|
+
let classIndex = 0;
|
|
275
|
+
let memberIndex = 0;
|
|
276
|
+
while (classIndex < pendingClasses.length || memberIndex < pendingMembers.length) {
|
|
277
|
+
if (classIndex < pendingClasses.length)
|
|
278
|
+
visitClass(pendingClasses[classIndex++]);
|
|
279
|
+
else
|
|
280
|
+
visitMember(pendingMembers[memberIndex++]);
|
|
170
281
|
}
|
|
171
282
|
return meanings;
|
|
172
283
|
}
|
|
173
|
-
function
|
|
174
|
-
const
|
|
284
|
+
function requireOwner(context, ref, key) {
|
|
285
|
+
const owner = context.schemas.get(ref.origin);
|
|
286
|
+
if (owner === undefined) {
|
|
287
|
+
throw contextInvalid(`Retained acceptance context cannot resolve ${key}.`, key);
|
|
288
|
+
}
|
|
289
|
+
return owner;
|
|
290
|
+
}
|
|
291
|
+
function contextInvalid(message, key) {
|
|
292
|
+
return new SchemaError([diagnostic('DM_DEPENDENCY_CONTEXT_INVALID', '', message, key)]);
|
|
293
|
+
}
|
|
294
|
+
function collectCoordinateSemanticRefs(context, coordinate, add) {
|
|
295
|
+
const owner = context.schemas.get(coordinate.owner.origin);
|
|
296
|
+
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.owner);
|
|
175
297
|
if (analysis === undefined)
|
|
176
298
|
return;
|
|
177
299
|
if (coordinate.kind === 'property') {
|
|
178
300
|
const field = analysis.properties.get(coordinate.key);
|
|
179
301
|
if (field !== undefined)
|
|
180
|
-
collectValueDefinitionRefs(
|
|
302
|
+
collectValueDefinitionRefs(field.schema, add);
|
|
181
303
|
return;
|
|
182
304
|
}
|
|
183
305
|
const method = [...analysis.methods.values()].find((candidate) => MethodKey.of(candidate.owner, candidate.name) === coordinate.key);
|
|
184
306
|
if (method !== undefined)
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
function collectEffectiveMeanings(analysis, dependencyOrigin, meanings, add) {
|
|
188
|
-
for (const field of analysis.properties.values()) {
|
|
189
|
-
const coordinate = propertyCoordinate(field.owner, field.name);
|
|
190
|
-
retainMeaning(meanings, dependencyOrigin, coordinate, propertyMeaning(field));
|
|
191
|
-
add(field.owner);
|
|
192
|
-
collectValueDefinitionRefs(semanticValueSchema(field.schema), add);
|
|
193
|
-
}
|
|
194
|
-
for (const method of analysis.methods.values()) {
|
|
195
|
-
const coordinate = methodCoordinate(method.owner, method.name);
|
|
196
|
-
retainMeaning(meanings, dependencyOrigin, coordinate, methodMeaning(method));
|
|
197
|
-
add(method.owner);
|
|
198
|
-
collectCallableRefs(semanticMethod(method.declaration), add);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
function retainMeaning(meanings, dependencyOrigin, coordinate, meaning) {
|
|
202
|
-
if (Key.origin(coordinate.key) !== dependencyOrigin)
|
|
203
|
-
return;
|
|
204
|
-
const fingerprint = fingerprintMeaning(meaning);
|
|
205
|
-
const previous = meanings.get(coordinate.key);
|
|
206
|
-
if (previous !== undefined && previous.fingerprint !== fingerprint) {
|
|
207
|
-
throw new SchemaError([
|
|
208
|
-
diagnostic('DM_MEANING_INCONSISTENT', '', `Semantic coordinate ${coordinate.key} produced inconsistent effective meaning.`, coordinate.key),
|
|
209
|
-
]);
|
|
210
|
-
}
|
|
211
|
-
meanings.set(coordinate.key, Object.freeze({ coordinate, fingerprint }));
|
|
212
|
-
}
|
|
213
|
-
function collectSchemaSemanticRefs(owner) {
|
|
214
|
-
const refs = new Map();
|
|
215
|
-
const add = (ref) => {
|
|
216
|
-
const member = requireMemberRef(ref);
|
|
217
|
-
refs.set(definitionRefIndex(member), member);
|
|
218
|
-
};
|
|
219
|
-
for (const declaration of Object.values(owner.classes)) {
|
|
220
|
-
collectClassRefs(semanticDefinition(declaration), add);
|
|
221
|
-
}
|
|
222
|
-
for (const declaration of Object.values(owner.functions)) {
|
|
223
|
-
collectCallableRefs(semanticFunction(declaration), add);
|
|
224
|
-
}
|
|
225
|
-
for (const declaration of Object.values(owner.policies)) {
|
|
226
|
-
collectPolicyDefinitionRefs(semanticPolicy(declaration), add);
|
|
227
|
-
}
|
|
228
|
-
for (const declaration of Object.values(owner.views)) {
|
|
229
|
-
if (declaration.target.kind === 'definition')
|
|
230
|
-
declaration.target.definitions.forEach(add);
|
|
231
|
-
}
|
|
232
|
-
for (const node of Object.values(owner.core.nodes))
|
|
233
|
-
add(node.class);
|
|
234
|
-
for (const edge of owner.core.edges) {
|
|
235
|
-
add(edge.class);
|
|
236
|
-
if (edge.source.kind !== 'domain')
|
|
237
|
-
add(edge.source);
|
|
238
|
-
if (edge.target.kind !== 'domain')
|
|
239
|
-
add(edge.target);
|
|
240
|
-
}
|
|
241
|
-
return [...refs.values()].sort(compareDefinitionRefs);
|
|
307
|
+
collectCallableContractRefs(method.declaration, add);
|
|
242
308
|
}
|
|
243
309
|
function collectMemberSemanticRefs(owner, ref, add) {
|
|
244
310
|
switch (ref.kind) {
|
|
245
|
-
case 'class': {
|
|
246
|
-
const declaration = owner.classes[ref.name];
|
|
247
|
-
if (declaration !== undefined) {
|
|
248
|
-
collectClassRefs(semanticDefinition(declaration), add);
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
311
|
case 'function': {
|
|
253
312
|
const declaration = owner.functions[ref.name];
|
|
254
313
|
if (declaration !== undefined)
|
|
255
|
-
collectCallableRefs(
|
|
314
|
+
collectCallableRefs(declaration, add);
|
|
256
315
|
return;
|
|
257
316
|
}
|
|
258
317
|
case 'policy': {
|
|
259
318
|
const declaration = owner.policies[ref.name];
|
|
260
|
-
if (declaration !== undefined)
|
|
261
|
-
collectPolicyDefinitionRefs(
|
|
262
|
-
}
|
|
319
|
+
if (declaration !== undefined)
|
|
320
|
+
collectPolicyDefinitionRefs(declaration, add);
|
|
263
321
|
return;
|
|
264
322
|
}
|
|
265
323
|
case 'view': {
|
|
266
324
|
const declaration = owner.views[ref.name];
|
|
267
|
-
if (declaration?.target.kind === 'definition')
|
|
268
|
-
declaration.target.definitions.forEach(add);
|
|
325
|
+
if (declaration?.target.kind === 'definition') {
|
|
326
|
+
declaration.target.definitions.forEach((definition) => add(definition, 'view'));
|
|
327
|
+
}
|
|
269
328
|
return;
|
|
270
329
|
}
|
|
271
330
|
case 'core': {
|
|
331
|
+
// The dependency instantiates its own Core; the dependent only refers to it.
|
|
272
332
|
const node = owner.core.nodes[ref.name];
|
|
273
333
|
if (node === undefined)
|
|
274
334
|
return;
|
|
@@ -283,12 +343,41 @@ function collectMemberSemanticRefs(owner, ref, add) {
|
|
|
283
343
|
}
|
|
284
344
|
}
|
|
285
345
|
}
|
|
286
|
-
function
|
|
287
|
-
if (Key.origin(coordinate.key) !==
|
|
346
|
+
function retainMeaning(meanings, selectedOrigin, coordinate, meaning, requirement) {
|
|
347
|
+
if (Key.origin(coordinate.key) !== selectedOrigin)
|
|
348
|
+
return;
|
|
349
|
+
const fingerprint = fingerprintMeaning(meaning);
|
|
350
|
+
const previous = meanings.get(coordinate.key);
|
|
351
|
+
if (previous !== undefined) {
|
|
352
|
+
const upgraded = previous.coordinate.kind === 'class' &&
|
|
353
|
+
coordinate.kind === 'class' &&
|
|
354
|
+
previous.coordinate.level !== coordinate.level;
|
|
355
|
+
if (upgraded) {
|
|
356
|
+
// A stronger requirement replaces the weaker one for the same Class.
|
|
357
|
+
if (coordinate.level === 'reference')
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
else if (previous.fingerprint !== fingerprint) {
|
|
361
|
+
throw new SchemaError([
|
|
362
|
+
diagnostic('DM_MEANING_INCONSISTENT', '', `Semantic coordinate ${coordinate.key} produced inconsistent effective meaning.`, coordinate.key),
|
|
363
|
+
]);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
meanings.set(coordinate.key, Object.freeze({
|
|
367
|
+
coordinate,
|
|
368
|
+
fingerprint,
|
|
369
|
+
...(requirement === undefined ? {} : { requirement }),
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
function meaningForCoordinate(context, coordinate, requirement) {
|
|
373
|
+
const owner = context.schemas.get(Key.origin(coordinate.key));
|
|
374
|
+
if (owner === undefined)
|
|
288
375
|
return undefined;
|
|
376
|
+
if (coordinate.kind === 'class')
|
|
377
|
+
return classMeaning(context, coordinate, requirement)?.meaning;
|
|
289
378
|
if (coordinate.kind === 'member')
|
|
290
379
|
return memberMeaning(owner, coordinate.ref);
|
|
291
|
-
const analysis = effectiveDefinition(owner, coordinate.owner);
|
|
380
|
+
const analysis = effectiveDefinition(context, owner, coordinate.owner);
|
|
292
381
|
if (analysis === undefined)
|
|
293
382
|
return undefined;
|
|
294
383
|
if (coordinate.kind === 'property') {
|
|
@@ -298,12 +387,69 @@ function meaningForCoordinate(owner, coordinate) {
|
|
|
298
387
|
const method = [...analysis.methods.values()].find((candidate) => MethodKey.of(candidate.owner, candidate.name) === coordinate.key);
|
|
299
388
|
return method === undefined ? undefined : methodMeaning(method);
|
|
300
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* The requirement one usage level places on a Class. A reference needs the Class to exist with
|
|
392
|
+
* its kind; inheritance additionally needs the source ancestors, class policies, data contract
|
|
393
|
+
* and, for an Edge, the endpoint contract. A target projected onto a source requirement keeps only
|
|
394
|
+
* the ancestors the source named, so a superset satisfies it while a removal changes it.
|
|
395
|
+
*/
|
|
396
|
+
function classMeaning(context, coordinate, onto) {
|
|
397
|
+
const owner = context.schemas.get(coordinate.ref.origin);
|
|
398
|
+
const declaration = owner?.classes[coordinate.ref.name];
|
|
399
|
+
const analysis = owner === undefined ? undefined : effectiveDefinition(context, owner, coordinate.ref);
|
|
400
|
+
if (owner === undefined || declaration === undefined || analysis === undefined)
|
|
401
|
+
return undefined;
|
|
402
|
+
const ancestors = ancestorKeys(context, coordinate.ref);
|
|
403
|
+
const requirement = Object.freeze({
|
|
404
|
+
ancestors,
|
|
405
|
+
properties: new Set(analysis.properties.keys()),
|
|
406
|
+
});
|
|
407
|
+
const meaning = {
|
|
408
|
+
kind: 'class',
|
|
409
|
+
level: coordinate.level,
|
|
410
|
+
classKind: declaration.kind,
|
|
411
|
+
...(declaration.kind === 'edge' ? { orientation: declaration.orientation } : {}),
|
|
412
|
+
...(coordinate.level === 'inherit'
|
|
413
|
+
? {
|
|
414
|
+
ancestors: onto === undefined
|
|
415
|
+
? ancestors
|
|
416
|
+
: onto.ancestors.filter((ancestor) => ancestors.includes(ancestor)),
|
|
417
|
+
policies: Object.fromEntries(Object.entries(declaration.policies)
|
|
418
|
+
.filter(([, ref]) => ref !== undefined)
|
|
419
|
+
.map(([role, ref]) => [role, keyForDefinitionRef(ref)])),
|
|
420
|
+
data: analysis.data === undefined
|
|
421
|
+
? null
|
|
422
|
+
: {
|
|
423
|
+
owner: keyForDefinitionRef(analysis.data.owner),
|
|
424
|
+
declaration: analysis.data.declaration,
|
|
425
|
+
},
|
|
426
|
+
...(declaration.kind === 'edge' ? { endpoints: declaration.endpoints } : {}),
|
|
427
|
+
}
|
|
428
|
+
: {}),
|
|
429
|
+
};
|
|
430
|
+
return { meaning, requirement };
|
|
431
|
+
}
|
|
432
|
+
function ancestorKeys(context, ref) {
|
|
433
|
+
const seen = new Set();
|
|
434
|
+
const visit = (current) => {
|
|
435
|
+
const declaration = context.schemas.get(current.origin)?.classes[current.name];
|
|
436
|
+
if (declaration === undefined)
|
|
437
|
+
return;
|
|
438
|
+
for (const parent of declaration.extends) {
|
|
439
|
+
const key = keyForDefinitionRef(parent);
|
|
440
|
+
if (seen.has(key))
|
|
441
|
+
continue;
|
|
442
|
+
seen.add(key);
|
|
443
|
+
visit(parent);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
visit(ref);
|
|
447
|
+
return [...seen].sort(compareUnicode);
|
|
448
|
+
}
|
|
301
449
|
function memberMeaning(owner, ref) {
|
|
302
450
|
if (ref.origin !== owner.origin)
|
|
303
451
|
return undefined;
|
|
304
452
|
switch (ref.kind) {
|
|
305
|
-
case 'class':
|
|
306
|
-
return definitionMeaning(owner, ref);
|
|
307
453
|
case 'function': {
|
|
308
454
|
const declaration = owner.functions[ref.name];
|
|
309
455
|
return declaration === undefined
|
|
@@ -334,35 +480,8 @@ function memberMeaning(owner, ref) {
|
|
|
334
480
|
}
|
|
335
481
|
}
|
|
336
482
|
}
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
const effective = effectiveDefinition(owner, ref);
|
|
340
|
-
if (declaration === undefined || effective === undefined || ref.origin !== owner.origin)
|
|
341
|
-
return undefined;
|
|
342
|
-
return {
|
|
343
|
-
kind: ref.kind,
|
|
344
|
-
declaration: semanticDefinition(declaration),
|
|
345
|
-
effective: {
|
|
346
|
-
properties: [...effective.properties.keys()].sort(compareUnicode),
|
|
347
|
-
methods: [...effective.methods.values()]
|
|
348
|
-
.map((method) => MethodKey.of(method.owner, method.name))
|
|
349
|
-
.sort(compareUnicode),
|
|
350
|
-
...(effective.data === undefined
|
|
351
|
-
? {}
|
|
352
|
-
: {
|
|
353
|
-
data: {
|
|
354
|
-
owner: keyForDefinitionRef(effective.data.owner),
|
|
355
|
-
declaration: effective.data.declaration,
|
|
356
|
-
},
|
|
357
|
-
}),
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
function effectiveDefinition(owner, ref) {
|
|
362
|
-
const validation = validateDomainSchemaSemantics(owner, lookupForDomainClosure(owner));
|
|
363
|
-
if (validation.diagnostics.length > 0)
|
|
364
|
-
return undefined;
|
|
365
|
-
return validation.analysis.definitions.get(definitionRefIndex(ref));
|
|
483
|
+
function effectiveDefinition(context, owner, ref) {
|
|
484
|
+
return context.analysis(owner)?.get(definitionRefIndex(ref));
|
|
366
485
|
}
|
|
367
486
|
function propertyMeaning(field) {
|
|
368
487
|
return {
|
|
@@ -382,37 +501,17 @@ function methodMeaning(method) {
|
|
|
382
501
|
declaration: semanticMethod(method.declaration),
|
|
383
502
|
};
|
|
384
503
|
}
|
|
385
|
-
function semanticDefinition(declaration) {
|
|
386
|
-
const { description: _description, ...rest } = declaration;
|
|
387
|
-
const semantic = {
|
|
388
|
-
...rest,
|
|
389
|
-
properties: Object.fromEntries(Object.entries(declaration.properties).map(([name, property]) => [
|
|
390
|
-
name,
|
|
391
|
-
{
|
|
392
|
-
...property,
|
|
393
|
-
schema: semanticValueSchema(property.schema),
|
|
394
|
-
},
|
|
395
|
-
])),
|
|
396
|
-
};
|
|
397
|
-
if ('methods' in declaration) {
|
|
398
|
-
semantic.methods = Object.fromEntries(Object.entries(declaration.methods).map(([name, method]) => [name, semanticMethod(method)]));
|
|
399
|
-
}
|
|
400
|
-
return semantic;
|
|
401
|
-
}
|
|
402
504
|
function semanticFunction(declaration) {
|
|
403
|
-
const { description: _description, ...rest } = declaration;
|
|
404
505
|
return {
|
|
405
|
-
...rest,
|
|
406
506
|
input: semanticValueSchema(declaration.input),
|
|
407
507
|
output: semanticOutput(declaration.output),
|
|
408
508
|
};
|
|
409
509
|
}
|
|
410
510
|
function semanticMethod(declaration) {
|
|
411
|
-
const { description: _description, ...rest } = declaration;
|
|
412
511
|
return {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
512
|
+
static: declaration.static,
|
|
513
|
+
abstract: declaration.abstract,
|
|
514
|
+
...semanticFunction(declaration),
|
|
416
515
|
};
|
|
417
516
|
}
|
|
418
517
|
function semanticOutput(output) {
|
|
@@ -458,7 +557,7 @@ function semanticValueSchema(value) {
|
|
|
458
557
|
}
|
|
459
558
|
return semantic;
|
|
460
559
|
}
|
|
461
|
-
function coordinateForKey(source, input, index) {
|
|
560
|
+
function coordinateForKey(context, source, input, index) {
|
|
462
561
|
let key;
|
|
463
562
|
try {
|
|
464
563
|
key = Key(input);
|
|
@@ -474,72 +573,45 @@ function coordinateForKey(source, input, index) {
|
|
|
474
573
|
if ((reference.member === 'property' || reference.member === 'method') &&
|
|
475
574
|
reference.ownerKind === 'class' &&
|
|
476
575
|
reference.ownerName !== undefined) {
|
|
477
|
-
|
|
478
|
-
if (owner === undefined) {
|
|
576
|
+
if (source.classes[reference.ownerName] === undefined) {
|
|
479
577
|
return invalidMemberRoot(key, index, 'has no exact owning Definition');
|
|
480
578
|
}
|
|
579
|
+
const owner = { origin: source.origin, kind: 'class', name: reference.ownerName };
|
|
481
580
|
coordinate =
|
|
482
581
|
reference.member === 'property'
|
|
483
582
|
? propertyCoordinate(owner, reference.name)
|
|
484
583
|
: methodCoordinate(owner, reference.name);
|
|
485
584
|
}
|
|
486
|
-
else if (reference.kind
|
|
487
|
-
|
|
488
|
-
origin: source.origin,
|
|
489
|
-
kind: reference.kind,
|
|
490
|
-
name: reference.name,
|
|
491
|
-
});
|
|
492
|
-
if (member === undefined)
|
|
585
|
+
else if (reference.kind === 'class') {
|
|
586
|
+
if (source.classes[reference.name] === undefined) {
|
|
493
587
|
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
494
|
-
|
|
588
|
+
}
|
|
589
|
+
// An explicit Class root compares its surface; it never infers an inheritance contract.
|
|
590
|
+
coordinate = classCoordinate({ origin: source.origin, kind: 'class', name: reference.name }, 'reference');
|
|
495
591
|
}
|
|
496
592
|
else {
|
|
497
593
|
coordinate = memberCoordinate({
|
|
498
594
|
origin: source.origin,
|
|
499
|
-
kind:
|
|
595
|
+
kind: reference.kind,
|
|
500
596
|
name: reference.name,
|
|
501
597
|
});
|
|
502
598
|
}
|
|
503
|
-
if (meaningForCoordinate(
|
|
599
|
+
if (meaningForCoordinate(context, coordinate) === undefined) {
|
|
504
600
|
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
505
601
|
}
|
|
506
602
|
return coordinate;
|
|
507
603
|
}
|
|
508
|
-
function memberRefFor(source, ref) {
|
|
509
|
-
const name = ref.name;
|
|
510
|
-
const owner = definitionRefFor(source, ref.kind, name);
|
|
511
|
-
if (owner !== undefined)
|
|
512
|
-
return owner;
|
|
513
|
-
switch (ref.kind) {
|
|
514
|
-
case 'function':
|
|
515
|
-
return source.functions[name] === undefined
|
|
516
|
-
? undefined
|
|
517
|
-
: { origin: source.origin, kind: 'function', name };
|
|
518
|
-
case 'policy':
|
|
519
|
-
return source.policies[name] === undefined
|
|
520
|
-
? undefined
|
|
521
|
-
: { origin: source.origin, kind: 'policy', name };
|
|
522
|
-
case 'view':
|
|
523
|
-
return source.views[name] === undefined
|
|
524
|
-
? undefined
|
|
525
|
-
: { origin: source.origin, kind: 'view', name };
|
|
526
|
-
default:
|
|
527
|
-
return undefined;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
function definitionRefFor(source, namespace, name) {
|
|
531
|
-
if (namespace === 'class') {
|
|
532
|
-
return source.classes[name] === undefined
|
|
533
|
-
? undefined
|
|
534
|
-
: { origin: source.origin, kind: 'class', name };
|
|
535
|
-
}
|
|
536
|
-
return undefined;
|
|
537
|
-
}
|
|
538
604
|
function invalidMemberRoot(key, index, reason) {
|
|
539
605
|
throw new SchemaError([
|
|
540
606
|
diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Member comparison root ${JSON.stringify(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
|
|
541
607
|
]);
|
|
542
608
|
}
|
|
609
|
+
function levelOf(site) {
|
|
610
|
+
return site === 'extends' || site === 'core' ? 'inherit' : 'reference';
|
|
611
|
+
}
|
|
612
|
+
function classCoordinate(ref, level) {
|
|
613
|
+
return Object.freeze({ kind: 'class', key: keyForDefinitionRef(ref), ref, level });
|
|
614
|
+
}
|
|
543
615
|
function memberCoordinate(ref) {
|
|
544
616
|
return Object.freeze({ kind: 'member', key: keyForDefinitionRef(ref), ref });
|
|
545
617
|
}
|
|
@@ -585,21 +657,6 @@ function incidentCoreEdges(owner, name) {
|
|
|
585
657
|
function fingerprintMeaning(value) {
|
|
586
658
|
return digestDomainSchemaBytes(json.encode(value));
|
|
587
659
|
}
|
|
588
|
-
function isDefinitionRef(ref) {
|
|
589
|
-
return ref.kind === 'class';
|
|
590
|
-
}
|
|
591
|
-
function requireMemberRef(ref) {
|
|
592
|
-
switch (ref.kind) {
|
|
593
|
-
case 'class':
|
|
594
|
-
case 'function':
|
|
595
|
-
case 'policy':
|
|
596
|
-
case 'view':
|
|
597
|
-
return ref;
|
|
598
|
-
case 'core':
|
|
599
|
-
return ref;
|
|
600
|
-
}
|
|
601
|
-
return ref;
|
|
602
|
-
}
|
|
603
660
|
function isJsonSchema(value) {
|
|
604
661
|
return typeof value === 'boolean' || isJsonObject(value);
|
|
605
662
|
}
|
|
@@ -11,8 +11,8 @@ export type { SchemaDiagnosticCode as DiagnosticCode } from './diagnostic.js';
|
|
|
11
11
|
export { acceptDomainSchema as accept } from './admission/accept.js';
|
|
12
12
|
export { decodeDomainSchema as decode, encodeDomainSchema as encode, serializeDomainSchema as serialize, } from './codec/schema.js';
|
|
13
13
|
export { revisionOfDomainSchema as revision } from './canonical/revision.js';
|
|
14
|
-
export { compareDependencyMeaning, compareMemberMeaning } from './compatibility/meaning.js';
|
|
15
|
-
export type { DependencyMeaningChange, DependencyMeaningChangeKind, DependencyMeaningComparison, MeaningFingerprint, } from './compatibility/meaning.js';
|
|
14
|
+
export { callableFingerprint, compareDependencyMeaning, compareMemberMeaning, } from './compatibility/meaning.js';
|
|
15
|
+
export type { DependencyMeaningChange, DependencyMeaningChangeKind, DependencyMeaningComparison, DependencyMeaningLevel, MeaningFingerprint, } from './compatibility/meaning.js';
|
|
16
16
|
export { header } from './codec/header.js';
|
|
17
17
|
export { DOMAIN_FORMAT as FORMAT, DOMAIN_VERSION as VERSION } from './model/format.js';
|
|
18
18
|
export { compile, load } from '../compiled/index.js';
|
package/dist/v1/schema/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export const resolve = resolveDomainSchema;
|
|
|
4
4
|
export { acceptDomainSchema as accept } from './admission/accept.js';
|
|
5
5
|
export { decodeDomainSchema as decode, encodeDomainSchema as encode, serializeDomainSchema as serialize, } from './codec/schema.js';
|
|
6
6
|
export { revisionOfDomainSchema as revision } from './canonical/revision.js';
|
|
7
|
-
export { compareDependencyMeaning, compareMemberMeaning } from './compatibility/meaning.js';
|
|
7
|
+
export { callableFingerprint, compareDependencyMeaning, compareMemberMeaning, } from './compatibility/meaning.js';
|
|
8
8
|
export { header } from './codec/header.js';
|
|
9
9
|
export { DOMAIN_FORMAT as FORMAT, DOMAIN_VERSION as VERSION } from './model/format.js';
|
|
10
10
|
export { compile, load } from '../compiled/index.js';
|
|
@@ -4,11 +4,24 @@ import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
|
4
4
|
import type { PolicyDefinition } from '../../language/policy/model/policy.js';
|
|
5
5
|
import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
6
6
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
7
|
+
/**
|
|
8
|
+
* Where one schema references a Definition. `extends` and `core` mean the schema inherits from or
|
|
9
|
+
* instantiates the Definition; every other site only refers to it.
|
|
10
|
+
*/
|
|
11
|
+
export type ReferenceSite = 'extends' | 'endpoint' | 'value' | 'policy' | 'view' | 'core';
|
|
12
|
+
export type ReferenceCollector = (ref: DefinitionRef, site: ReferenceSite) => void;
|
|
7
13
|
/** Collect only semantic refs, never strings or objects embedded as JSON data. */
|
|
8
14
|
export declare function collectUsedForeignOrigins(schema: DomainSchemaIR): ReadonlySet<string>;
|
|
9
15
|
/** Complete structured references in one schema, in canonical order. */
|
|
10
16
|
export declare function collectDomainDefinitionRefs(schema: DomainSchemaIR): readonly DefinitionRef[];
|
|
11
|
-
export declare function
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function
|
|
14
|
-
export declare function
|
|
17
|
+
export declare function collectCoreRefs(schema: DomainSchemaIR, add: ReferenceCollector): void;
|
|
18
|
+
export declare function collectClassRefs(declaration: ClassDefinition, add: ReferenceCollector): void;
|
|
19
|
+
export declare function collectClassPolicyRefs(declaration: ClassDefinition, add: ReferenceCollector): void;
|
|
20
|
+
export declare function collectEndpointRefs(declaration: Extract<ClassDefinition, {
|
|
21
|
+
readonly kind: 'edge';
|
|
22
|
+
}>, add: ReferenceCollector): void;
|
|
23
|
+
export declare function collectCallableRefs(declaration: FunctionDefinition | MethodDefinition, add: ReferenceCollector): void;
|
|
24
|
+
/** References inside the contract a caller compiles against: the input and output value schemas. */
|
|
25
|
+
export declare function collectCallableContractRefs(declaration: Pick<FunctionDefinition | MethodDefinition, 'input' | 'output'>, add: ReferenceCollector): void;
|
|
26
|
+
export declare function collectPolicyDefinitionRefs(declaration: PolicyDefinition, add: ReferenceCollector): void;
|
|
27
|
+
export declare function collectValueDefinitionRefs(schema: JsonSchema, add: ReferenceCollector): void;
|
|
@@ -24,48 +24,59 @@ export function collectDomainDefinitionRefs(schema) {
|
|
|
24
24
|
collectPolicyDefinitionRefs(declaration, add);
|
|
25
25
|
}
|
|
26
26
|
for (const declaration of Object.values(schema.views)) {
|
|
27
|
-
if (declaration.target.kind === 'definition')
|
|
28
|
-
declaration.target.definitions.forEach(add);
|
|
27
|
+
if (declaration.target.kind === 'definition') {
|
|
28
|
+
declaration.target.definitions.forEach((ref) => add(ref, 'view'));
|
|
29
|
+
}
|
|
29
30
|
}
|
|
31
|
+
collectCoreRefs(schema, add);
|
|
32
|
+
return Object.freeze([...result.values()].sort(compareDefinitionRefs));
|
|
33
|
+
}
|
|
34
|
+
export function collectCoreRefs(schema, add) {
|
|
30
35
|
for (const node of Object.values(schema.core.nodes))
|
|
31
|
-
add(node.class);
|
|
36
|
+
add(node.class, 'core');
|
|
32
37
|
for (const edge of schema.core.edges) {
|
|
33
|
-
add(edge.class);
|
|
38
|
+
add(edge.class, 'core');
|
|
34
39
|
if (edge.source.kind !== 'domain')
|
|
35
|
-
add(edge.source);
|
|
40
|
+
add(edge.source, 'core');
|
|
36
41
|
if (edge.target.kind !== 'domain')
|
|
37
|
-
add(edge.target);
|
|
42
|
+
add(edge.target, 'core');
|
|
38
43
|
}
|
|
39
|
-
return Object.freeze([...result.values()].sort(compareDefinitionRefs));
|
|
40
44
|
}
|
|
41
45
|
export function collectClassRefs(declaration, add) {
|
|
42
|
-
declaration.extends.forEach(add);
|
|
46
|
+
declaration.extends.forEach((ref) => add(ref, 'extends'));
|
|
43
47
|
Object.values(declaration.properties).forEach((property) => collectValueDefinitionRefs(property.schema, add));
|
|
44
|
-
|
|
45
|
-
add(declaration.policies.read);
|
|
46
|
-
if (declaration.policies.traverse !== undefined)
|
|
47
|
-
add(declaration.policies.traverse);
|
|
48
|
+
collectClassPolicyRefs(declaration, add);
|
|
48
49
|
if (declaration.kind === 'edge') {
|
|
49
50
|
collectEndpointRefs(declaration, add);
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
Object.values(declaration.methods).forEach((method) => collectCallableRefs(method, add));
|
|
53
54
|
}
|
|
54
|
-
function
|
|
55
|
+
export function collectClassPolicyRefs(declaration, add) {
|
|
56
|
+
if (declaration.policies.read !== undefined)
|
|
57
|
+
add(declaration.policies.read, 'policy');
|
|
58
|
+
if (declaration.policies.traverse !== undefined)
|
|
59
|
+
add(declaration.policies.traverse, 'policy');
|
|
60
|
+
}
|
|
61
|
+
export function collectEndpointRefs(declaration, add) {
|
|
55
62
|
const endpoints = declaration.orientation === 'directed'
|
|
56
63
|
? [declaration.endpoints.source, declaration.endpoints.target]
|
|
57
64
|
: declaration.endpoints;
|
|
58
|
-
endpoints[0].accepts.forEach(add);
|
|
59
|
-
endpoints[1].accepts.forEach(add);
|
|
65
|
+
endpoints[0].accepts.forEach((ref) => add(ref, 'endpoint'));
|
|
66
|
+
endpoints[1].accepts.forEach((ref) => add(ref, 'endpoint'));
|
|
60
67
|
}
|
|
61
68
|
export function collectCallableRefs(declaration, add) {
|
|
69
|
+
collectCallableContractRefs(declaration, add);
|
|
70
|
+
if (declaration.policy !== undefined)
|
|
71
|
+
collectPolicyCheckRefs(declaration.policy, add);
|
|
72
|
+
}
|
|
73
|
+
/** References inside the contract a caller compiles against: the input and output value schemas. */
|
|
74
|
+
export function collectCallableContractRefs(declaration, add) {
|
|
62
75
|
collectValueDefinitionRefs(declaration.input, add);
|
|
63
76
|
if (declaration.output.mode === 'value')
|
|
64
77
|
collectValueDefinitionRefs(declaration.output.schema, add);
|
|
65
78
|
if (declaration.output.mode === 'stream')
|
|
66
79
|
collectValueDefinitionRefs(declaration.output.item, add);
|
|
67
|
-
if (declaration.policy !== undefined)
|
|
68
|
-
collectPolicyCheckRefs(declaration.policy, add);
|
|
69
80
|
}
|
|
70
81
|
export function collectPolicyDefinitionRefs(declaration, add) {
|
|
71
82
|
collectPolicyExpressionRefs(declaration.expression, add);
|
|
@@ -76,27 +87,27 @@ function collectPolicyExpressionRefs(expression, add) {
|
|
|
76
87
|
return;
|
|
77
88
|
}
|
|
78
89
|
const refs = 'allOf' in expression ? expression.allOf : expression.anyOf;
|
|
79
|
-
refs.forEach(add);
|
|
90
|
+
refs.forEach((ref) => add(ref, 'policy'));
|
|
80
91
|
}
|
|
81
92
|
function collectPolicyPatternRefs(expression, add) {
|
|
82
93
|
if ('sameNode' in expression) {
|
|
83
94
|
for (const term of [expression.sameNode.left, expression.sameNode.right])
|
|
84
95
|
if (term.kind === 'ref')
|
|
85
|
-
add(term.ref.kind === 'method' ? term.ref.owner : term.ref);
|
|
96
|
+
add(term.ref.kind === 'method' ? term.ref.owner : term.ref, 'policy');
|
|
86
97
|
return;
|
|
87
98
|
}
|
|
88
99
|
if ('source' in expression) {
|
|
89
100
|
for (const term of [expression.source, expression.target])
|
|
90
101
|
if (term.kind === 'ref')
|
|
91
|
-
add(term.ref.kind === 'method' ? term.ref.owner : term.ref);
|
|
92
|
-
add(expression.class);
|
|
102
|
+
add(term.ref.kind === 'method' ? term.ref.owner : term.ref, 'policy');
|
|
103
|
+
add(expression.class, 'policy');
|
|
93
104
|
return;
|
|
94
105
|
}
|
|
95
106
|
if ('exists' in expression) {
|
|
96
107
|
expression.exists.nodes.forEach((node) => {
|
|
97
108
|
const ref = policyVariableClass(node);
|
|
98
109
|
if (ref !== undefined)
|
|
99
|
-
add(ref);
|
|
110
|
+
add(ref, 'policy');
|
|
100
111
|
});
|
|
101
112
|
collectPolicyPatternRefs(expression.exists.where, add);
|
|
102
113
|
return;
|
|
@@ -108,15 +119,15 @@ function collectPolicyCheckRefs(expression, add) {
|
|
|
108
119
|
if ('sameNode' in expression) {
|
|
109
120
|
for (const term of [expression.sameNode.left, expression.sameNode.right])
|
|
110
121
|
if (term.kind === 'ref')
|
|
111
|
-
add(term.ref.kind === 'method' ? term.ref.owner : term.ref);
|
|
122
|
+
add(term.ref.kind === 'method' ? term.ref.owner : term.ref, 'policy');
|
|
112
123
|
return;
|
|
113
124
|
}
|
|
114
125
|
if ('check' in expression) {
|
|
115
|
-
add(expression.check);
|
|
126
|
+
add(expression.check, 'policy');
|
|
116
127
|
if (expression.object.kind === 'ref')
|
|
117
128
|
add(expression.object.ref.kind === 'method'
|
|
118
129
|
? expression.object.ref.owner
|
|
119
|
-
: expression.object.ref);
|
|
130
|
+
: expression.object.ref, 'policy');
|
|
120
131
|
return;
|
|
121
132
|
}
|
|
122
133
|
const children = 'allOf' in expression ? expression.allOf : expression.anyOf;
|
|
@@ -134,7 +145,7 @@ export function collectValueDefinitionRefs(schema, add) {
|
|
|
134
145
|
typeof candidate.origin === 'string' &&
|
|
135
146
|
typeof candidate.kind === 'string' &&
|
|
136
147
|
typeof candidate.name === 'string') {
|
|
137
|
-
add(candidate);
|
|
148
|
+
add(candidate, 'value');
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
151
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/kernel-dsl",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.35",
|
|
4
4
|
"description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"generate:value-schema-validator": "node scripts/generate-value-schema-meta-validator.mjs",
|
|
106
106
|
"layout:check": "node scripts/check-layout.mjs",
|
|
107
107
|
"spec:typecheck": "tsgo --noEmit -p tsconfig.spec.json",
|
|
108
|
-
"typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.types.json",
|
|
108
|
+
"typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.types.json && tsgo -p tsconfig.workers.json",
|
|
109
109
|
"test": "pnpm run conformance && pnpm run layout:check && vitest run",
|
|
110
110
|
"test:file": "vitest run",
|
|
111
111
|
"test:semantic-lock": "pnpm run conformance && pnpm run layout:check && vitest run",
|