@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.
@@ -3,5 +3,8 @@ export interface IRegexpMatcher {
3
3
  test(value: string): boolean;
4
4
  matches(value: string): boolean;
5
5
  }
6
- /** Load portable NFA data without a regex engine or dynamic compilation. */
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
- /** Load portable NFA data without a regex engine or dynamic compilation. */
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 start = closure(pattern.states, new Set([pattern.start]));
6
+ const states = pattern.states;
7
+ const start = initialFrontier(pattern);
4
8
  const run = (input, search) => {
5
- let active = new Set(start);
6
- if ((search || input.length === 0) && accepts(pattern.states, active))
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
- const next = new Set();
11
- for (const index of active) {
12
- for (const transition of pattern.states[index].transitions) {
13
- if (transition.ranges.some(([minimum, maximum]) => scalar >= minimum && scalar <= maximum)) {
14
- next.add(transition.to);
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
- active = closure(pattern.states, next);
19
- if (search)
20
- for (const index of start)
21
- active.add(index);
22
- if (search && accepts(pattern.states, active))
23
- return true;
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 accepts(pattern.states, active);
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 closure(states, seed) {
33
- const output = new Set(seed);
34
- const pending = [...seed];
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
- for (const next of states[pending.pop()].epsilon) {
37
- if (output.has(next))
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
- output.add(next);
40
- pending.push(next);
102
+ seen.add(target);
103
+ pending.push(target);
41
104
  }
42
105
  }
43
- return output;
106
+ return { frontier: Object.freeze(frontier), accepting };
44
107
  }
45
- function accepts(states, active) {
46
- for (const index of active)
47
- if (states[index]?.accepting === true)
48
- return true;
49
- return false;
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
- export type DependencyMeaningChangeKind = 'missing' | 'changed';
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 interface DependencyMeaningChange {
14
+ export type DependencyMeaningChange = {
8
15
  readonly key: SemanticKey;
9
- readonly kind: DependencyMeaningChangeKind;
16
+ readonly kind: 'missing';
10
17
  readonly sourceFingerprint: MeaningFingerprint;
11
- readonly targetFingerprint?: MeaningFingerprint;
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: 1;
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 { compareDefinitionRefs, compareUnicode, definitionRefIndex, } from '../../addressing/ordering.js';
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 closure = knownDomainClosure(dependent) ?? [];
35
- const source = closure.find((candidate) => candidate.schema.origin === target.origin)?.schema;
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 schemas = new Map([
42
- [dependent.origin, dependent],
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 closure = knownDomainClosure(source) ?? [];
57
+ const context = contextOf(source);
58
+ const coordinates = roots.map((key, index) => coordinateForKey(context, source, key, index));
59
+ const meanings = collectReachableMeanings(coordinates, source.origin, context);
60
+ return compareSourceMeanings(source, target, meanings);
61
+ }
62
+ /**
63
+ * Fingerprint the contract a caller compiles against, independently of its Domain revision: the
64
+ * exact key, the owner, dispatch and abstractness of a Method, and the semantic input and output
65
+ * contracts; authorization is excluded (DM-CALLABLE-FINGERPRINT). A static consumer pins this
66
+ * instead of a Domain revision, so a dependency release that leaves the callable contract untouched
67
+ * keeps every bound invocation valid.
68
+ */
69
+ export function callableFingerprint(callable) {
70
+ return fingerprintMeaning('owner' in callable
71
+ ? {
72
+ kind: 'method',
73
+ key: callable.key,
74
+ owner: callable.owner.key,
75
+ ...semanticMethod(callable),
76
+ }
77
+ : { kind: 'function', key: callable.key, ...semanticFunction(callable) });
78
+ }
79
+ function contextOf(root) {
62
80
  const schemas = new Map([
63
- [source.origin, source],
64
- ...closure.map(({ schema: candidate }) => [candidate.origin, candidate]),
81
+ [root.origin, root],
82
+ ...(knownDomainClosure(root) ?? []).map(({ schema }) => [schema.origin, schema]),
65
83
  ]);
66
- const coordinates = roots.map((key, index) => coordinateForKey(source, key, index));
67
- const sourceMeanings = collectReachableMeanings(coordinates, source.origin, schemas);
68
- return compareSourceMeanings(source, target, sourceMeanings);
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(target, sourceMeaning.coordinate);
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: 1,
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: 1,
136
+ version: 2,
113
137
  dependency: source.origin,
114
138
  source: sourceRevision,
115
139
  target: targetRevision,
116
140
  footprint,
117
- changes: immutableChanges,
141
+ changes: Object.freeze([...changes, ...additions]),
118
142
  fingerprint,
119
143
  });
120
144
  }
121
- function collectDependencyMeanings(dependent, dependencyOrigin, schemas) {
122
- return collectReachableMeanings(collectSchemaSemanticRefs(dependent).map(memberCoordinate), dependencyOrigin, schemas);
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, schemas) {
197
+ function collectReachableMeanings(roots, selectedOrigin, context) {
125
198
  const meanings = new Map();
126
- const pending = [];
127
- const visited = new Set();
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
- pending.push(coordinate.ref);
215
+ pendingMembers.push(coordinate);
131
216
  continue;
132
217
  }
133
- const owner = schemas.get(Key.origin(coordinate.key));
134
- const meaning = owner === undefined ? undefined : meaningForCoordinate(owner, coordinate);
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(owner, coordinate, (candidate) => pending.push(requireMemberRef(candidate)));
225
+ collectCoordinateSemanticRefs(context, coordinate, push);
142
226
  }
143
- for (let index = 0; index < pending.length; index += 1) {
144
- const ref = pending[index];
145
- const refIndex = definitionRefIndex(ref);
146
- if (visited.has(refIndex))
147
- continue;
148
- visited.add(refIndex);
149
- const owner = schemas.get(ref.origin);
150
- if (owner === undefined) {
151
- throw new SchemaError([
152
- diagnostic('DM_DEPENDENCY_CONTEXT_INVALID', '', `Retained acceptance context cannot resolve ${keyForDefinitionRef(ref)}.`, keyForDefinitionRef(ref)),
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
- const primary = memberCoordinate(ref);
156
- const primaryMeaning = meaningForCoordinate(owner, primary);
157
- if (primaryMeaning === undefined) {
158
- throw new SchemaError([
159
- diagnostic('DM_DEPENDENCY_CONTEXT_INVALID', '', `Retained acceptance context has no meaning for ${primary.key}.`, primary.key),
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
- retainMeaning(meanings, selectedOrigin, primary, primaryMeaning);
163
- collectMemberSemanticRefs(owner, ref, (candidate) => pending.push(requireMemberRef(candidate)));
164
- if (!isDefinitionRef(ref))
165
- continue;
166
- const analysis = effectiveDefinition(owner, ref);
167
- if (analysis === undefined)
168
- continue;
169
- collectEffectiveMeanings(analysis, selectedOrigin, meanings, (candidate) => pending.push(requireMemberRef(candidate)));
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 collectCoordinateSemanticRefs(owner, coordinate, add) {
174
- const analysis = effectiveDefinition(owner, coordinate.owner);
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(semanticValueSchema(field.schema), add);
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
- collectCallableRefs(semanticMethod(method.declaration), add);
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(semanticFunction(declaration), add);
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(semanticPolicy(declaration), add);
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 meaningForCoordinate(owner, coordinate) {
287
- if (Key.origin(coordinate.key) !== owner.origin)
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 definitionMeaning(owner, ref) {
338
- const declaration = owner.classes[ref.name];
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
- ...rest,
414
- input: semanticValueSchema(declaration.input),
415
- output: semanticOutput(declaration.output),
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
- const owner = definitionRefFor(source, reference.ownerKind, reference.ownerName);
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 !== 'core') {
487
- const member = memberRefFor(source, {
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
- coordinate = memberCoordinate(member);
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: 'core',
595
+ kind: reference.kind,
500
596
  name: reference.name,
501
597
  });
502
598
  }
503
- if (meaningForCoordinate(source, coordinate) === undefined) {
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';
@@ -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 collectClassRefs(declaration: ClassDefinition, add: (ref: DefinitionRef) => void): void;
12
- export declare function collectCallableRefs(declaration: FunctionDefinition | MethodDefinition, add: (ref: DefinitionRef) => void): void;
13
- export declare function collectPolicyDefinitionRefs(declaration: PolicyDefinition, add: (ref: DefinitionRef) => void): void;
14
- export declare function collectValueDefinitionRefs(schema: JsonSchema, add: (ref: DefinitionRef) => void): void;
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
- if (declaration.policies.read !== undefined)
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 collectEndpointRefs(declaration, add) {
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.33",
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",