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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/v1/language/callable/composition.d.ts +13 -1
  2. package/dist/v1/language/callable/composition.js +13 -3
  3. package/dist/v1/language/value/evaluate/evaluator.js +66 -10
  4. package/dist/v1/schema/analysis/class.d.ts +21 -0
  5. package/dist/v1/schema/analysis/class.js +18 -9
  6. package/dist/v1/schema/compatibility/api.d.ts +2 -0
  7. package/dist/v1/schema/compatibility/api.js +1 -0
  8. package/dist/v1/schema/compatibility/comparators/callables.d.ts +28 -0
  9. package/dist/v1/schema/compatibility/comparators/callables.js +77 -0
  10. package/dist/v1/schema/compatibility/comparators/classes.d.ts +28 -0
  11. package/dist/v1/schema/compatibility/comparators/classes.js +96 -0
  12. package/dist/v1/schema/compatibility/comparators/index.d.ts +12 -0
  13. package/dist/v1/schema/compatibility/comparators/index.js +41 -0
  14. package/dist/v1/schema/compatibility/comparators/members.d.ts +4 -0
  15. package/dist/v1/schema/compatibility/comparators/members.js +49 -0
  16. package/dist/v1/schema/compatibility/comparators/values.d.ts +5 -0
  17. package/dist/v1/schema/compatibility/comparators/values.js +37 -0
  18. package/dist/v1/schema/compatibility/compare.d.ts +5 -57
  19. package/dist/v1/schema/compatibility/compare.js +19 -162
  20. package/dist/v1/schema/compatibility/coordinate.d.ts +2 -7
  21. package/dist/v1/schema/compatibility/coordinate.js +2 -6
  22. package/dist/v1/schema/compatibility/evidence.d.ts +5 -0
  23. package/dist/v1/schema/compatibility/evidence.js +51 -0
  24. package/dist/v1/schema/compatibility/fields.d.ts +6 -0
  25. package/dist/v1/schema/compatibility/fields.js +5 -0
  26. package/dist/v1/schema/compatibility/index.d.ts +5 -4
  27. package/dist/v1/schema/compatibility/index.js +3 -2
  28. package/dist/v1/schema/compatibility/legacy/index.d.ts +58 -0
  29. package/dist/v1/schema/compatibility/legacy/index.js +145 -0
  30. package/dist/v1/schema/compatibility/meaning/footprint.d.ts +7 -0
  31. package/dist/v1/schema/compatibility/meaning/footprint.js +57 -0
  32. package/dist/v1/schema/compatibility/meaning/index.d.ts +3 -0
  33. package/dist/v1/schema/compatibility/meaning/index.js +14 -0
  34. package/dist/v1/schema/compatibility/model.d.ts +96 -0
  35. package/dist/v1/schema/compatibility/model.js +1 -0
  36. package/dist/v1/schema/compatibility/references.d.ts +30 -0
  37. package/dist/v1/schema/compatibility/references.js +122 -0
  38. package/dist/v1/schema/compatibility/report.d.ts +26 -0
  39. package/dist/v1/schema/compatibility/report.js +100 -0
  40. package/dist/v1/schema/compatibility/scope.d.ts +27 -0
  41. package/dist/v1/schema/compatibility/scope.js +78 -0
  42. package/dist/v1/schema/compatibility/structure/index.d.ts +3 -0
  43. package/dist/v1/schema/compatibility/structure/index.js +16 -0
  44. package/dist/v1/schema/compatibility/structure/inheritance.d.ts +7 -0
  45. package/dist/v1/schema/compatibility/structure/inheritance.js +68 -0
  46. package/dist/v1/schema/compatibility/structure/requirements.d.ts +7 -0
  47. package/dist/v1/schema/compatibility/structure/requirements.js +70 -0
  48. package/dist/v1/schema/compatibility/subjects.d.ts +34 -0
  49. package/dist/v1/schema/compatibility/subjects.js +86 -0
  50. package/dist/v1/schema/fingerprint.d.ts +2 -2
  51. package/dist/v1/schema/fingerprint.js +1 -1
  52. package/dist/v1/schema/index.d.ts +3 -0
  53. package/dist/v1/schema/index.js +2 -0
  54. package/package.json +2 -2
  55. package/dist/v1/schema/compatibility/footprint.d.ts +0 -25
  56. package/dist/v1/schema/compatibility/footprint.js +0 -264
  57. package/dist/v1/schema/compatibility/meaning.d.ts +0 -44
  58. package/dist/v1/schema/compatibility/meaning.js +0 -234
@@ -0,0 +1,57 @@
1
+ import { Key } from '../../../addressing/index.js';
2
+ import { compareAddedProperties } from '../comparators/classes.js';
3
+ import { compareFacet } from '../comparators/index.js';
4
+ import { contextInvalid } from '../context.js';
5
+ import { callableReferences, classReferences, inherits, memberReferences, valueReferences, } from '../references.js';
6
+ /** Meaning follows complete callable contracts through the exact authored reference closure. */
7
+ export function compareFootprint({ context, report, }) {
8
+ const queue = [...context.roots];
9
+ const visited = new Set();
10
+ for (const reference of queue) {
11
+ report.note(reference);
12
+ const { coordinate, usage } = reference;
13
+ const visit = `${coordinate.key}:${usage.relation}`;
14
+ if (visited.has(visit))
15
+ continue;
16
+ visited.add(visit);
17
+ const pair = context.pair(coordinate);
18
+ const subject = pair.source;
19
+ if (subject === undefined)
20
+ throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
21
+ let facet;
22
+ switch (subject.kind) {
23
+ case 'class': {
24
+ facet = inherits(usage) ? 'class.inherit' : 'class.reference';
25
+ queue.push(...classReferences(subject, false));
26
+ if (inherits(usage))
27
+ queue.push(...classReferences(subject, true));
28
+ break;
29
+ }
30
+ case 'property':
31
+ facet = 'property';
32
+ queue.push(...valueReferences(subject));
33
+ break;
34
+ case 'method':
35
+ facet = 'method.signature';
36
+ queue.push(...callableReferences(subject));
37
+ break;
38
+ case 'member': {
39
+ facet =
40
+ subject.coordinate.ref.kind === 'function'
41
+ ? 'function.signature'
42
+ : subject.coordinate.ref.kind;
43
+ queue.push(...memberReferences(subject));
44
+ }
45
+ }
46
+ if (Key.origin(coordinate.key) !== context.source.origin)
47
+ continue;
48
+ report.record({
49
+ pair,
50
+ facet,
51
+ ...compareFacet({ context, pair, facet }),
52
+ requirement: { kind: facet },
53
+ });
54
+ if (facet === 'class.inherit')
55
+ compareAddedProperties({ context, pair, report });
56
+ }
57
+ }
@@ -0,0 +1,3 @@
1
+ import type { MeaningComparison } from '../model.js';
2
+ import type { ComparisonContext } from '../scope.js';
3
+ export declare function analyzeMeaning(context: ComparisonContext): MeaningComparison;
@@ -0,0 +1,14 @@
1
+ import { evidenceOf } from '../evidence.js';
2
+ import { createReport } from '../report.js';
3
+ import { compareFootprint } from './footprint.js';
4
+ export function analyzeMeaning(context) {
5
+ const report = createReport();
6
+ compareFootprint({ context, report });
7
+ const result = { scope: context.scope, ...report.finish() };
8
+ return Object.freeze({
9
+ kind: 'meaning',
10
+ ...result,
11
+ equivalent: result.assessments.every(({ satisfied }) => satisfied),
12
+ evidence: evidenceOf({ kind: 'meaning', ...result }),
13
+ });
14
+ }
@@ -0,0 +1,96 @@
1
+ import type { Value } from '@astrale-os/kernel-dsl/value';
2
+ import type { Definition, Key, Revision } from '../../addressing/index.js';
3
+ import type { DomainSchema } from '../model/schema.js';
4
+ import type { ReferenceSite } from '../resolution/origins.js';
5
+ import type { MeaningDifference } from './diff.js';
6
+ export type Fingerprint = `sha256:${string}`;
7
+ /** Inputs carry accepted schemas; a dependency's source is always its retained exact context. */
8
+ export interface ComparisonRequest {
9
+ readonly scope: {
10
+ readonly kind: 'dependency';
11
+ readonly dependent: DomainSchema;
12
+ } | {
13
+ readonly kind: 'members';
14
+ readonly source: DomainSchema;
15
+ readonly roots: readonly [Key, ...Key[]];
16
+ };
17
+ readonly target: DomainSchema;
18
+ }
19
+ export interface SchemaIdentity {
20
+ readonly origin: Key.Origin;
21
+ readonly revision: Revision;
22
+ }
23
+ export interface ComparisonScope {
24
+ readonly kind: ComparisonRequest['scope']['kind'];
25
+ readonly source: SchemaIdentity;
26
+ readonly target: SchemaIdentity;
27
+ readonly dependent?: SchemaIdentity;
28
+ readonly roots: readonly Key[];
29
+ }
30
+ /** Identity contains no requirement or usage level. */
31
+ export interface Subject {
32
+ readonly key: Key;
33
+ readonly kind: Definition.Kind | 'property' | 'method';
34
+ }
35
+ /** Immediate provenance, retained even when several paths impose the same requirement. */
36
+ export interface Usage {
37
+ readonly source: SchemaIdentity;
38
+ readonly key?: Key;
39
+ readonly relation: ReferenceSite | 'member' | 'abstract' | 'selection';
40
+ }
41
+ export type Facet = 'class.reference' | 'class.inherit' | 'class.composition' | 'property' | 'property.required' | 'method.existence' | 'method.inheritance' | 'method.signature' | 'function.existence' | 'function.signature' | 'policy' | 'view' | 'core';
42
+ /** A fact about a compared facet, including unchanged facets. No admission decision is implied. */
43
+ export interface Observation {
44
+ readonly id: string;
45
+ readonly facet: Facet;
46
+ readonly state: 'equal' | 'changed' | 'missing' | 'added';
47
+ readonly before?: Value;
48
+ readonly after?: Value;
49
+ readonly differences: readonly MeaningDifference[];
50
+ }
51
+ export interface ComparisonEntry {
52
+ readonly subject: Subject;
53
+ readonly source?: SchemaIdentity;
54
+ readonly target?: SchemaIdentity;
55
+ readonly usages: readonly Usage[];
56
+ readonly observations: readonly Observation[];
57
+ }
58
+ /** An established requirement; observations without one remain visible but do not affect the conclusion. */
59
+ export interface Assessment {
60
+ readonly subject: Subject;
61
+ readonly observation: Observation;
62
+ readonly requirement: {
63
+ readonly kind: Facet;
64
+ };
65
+ readonly satisfied: boolean;
66
+ }
67
+ /** Evidence is a semantic projection; presentation/provenance never enters its digest. */
68
+ export interface ComparisonEvidence {
69
+ readonly kind: 'structure' | 'meaning';
70
+ readonly version: 1 | 2;
71
+ readonly fingerprint: Fingerprint;
72
+ }
73
+ export interface Analysis {
74
+ readonly scope: ComparisonScope;
75
+ readonly entries: readonly ComparisonEntry[];
76
+ readonly assessments: readonly Assessment[];
77
+ readonly evidence: ComparisonEvidence;
78
+ }
79
+ export interface StructureComparison extends Analysis {
80
+ readonly kind: 'structure';
81
+ readonly compatible: boolean;
82
+ }
83
+ export interface MeaningComparison extends Analysis {
84
+ readonly kind: 'meaning';
85
+ readonly equivalent: boolean;
86
+ }
87
+ export interface Comparison {
88
+ readonly scope: ComparisonScope;
89
+ readonly entries: readonly ComparisonEntry[];
90
+ readonly structure: StructureComparison;
91
+ readonly meaning: MeaningComparison;
92
+ readonly evidence: {
93
+ readonly structure: ComparisonEvidence;
94
+ readonly meaning: ComparisonEvidence;
95
+ };
96
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import type { Key } from '../../addressing/index.js';
2
+ import type { DomainSchema } from '../model/schema.js';
3
+ import type { Coordinate } from './coordinate.js';
4
+ import type { Usage } from './model.js';
5
+ import type { ResolvedSubject } from './subjects.js';
6
+ export interface Reference {
7
+ readonly coordinate: Coordinate;
8
+ readonly usage: Usage;
9
+ }
10
+ export declare function schemaIdentity(schema: DomainSchema): Readonly<{
11
+ origin: string;
12
+ revision: import("../../addressing/revision.js").Revision;
13
+ }>;
14
+ export declare function usageOf(schema: DomainSchema, relation: Usage['relation'], key?: Key): Usage;
15
+ /** References keep their declaring member and relation; no strongest-usage collapse happens here. */
16
+ export declare function rootReferences(schema: DomainSchema): readonly Reference[];
17
+ /** Existing DSL collectors own reference syntax. These functions attach comparison provenance. */
18
+ export declare function valueReferences(subject: Extract<ResolvedSubject, {
19
+ kind: 'property';
20
+ }>): readonly Reference[];
21
+ export declare function callableReferences(subject: Extract<ResolvedSubject, {
22
+ kind: 'method';
23
+ }>): readonly Reference[];
24
+ export declare function classReferences(subject: Extract<ResolvedSubject, {
25
+ kind: 'class';
26
+ }>, inherit: boolean): readonly Reference[];
27
+ export declare function memberReferences(subject: Extract<ResolvedSubject, {
28
+ kind: 'member';
29
+ }>): readonly Reference[];
30
+ export declare function inherits(usage: Usage): boolean;
@@ -0,0 +1,122 @@
1
+ import { revisionOfDomainSchema } from '../canonical/revision.js';
2
+ import { collectCallableContractRefs, collectCallableRefs, collectClassPolicyRefs, collectCoreRefs, collectEndpointRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, coreEndpointDefinitionRef, } from '../resolution/origins.js';
3
+ import { classCoordinate, keyForDefinitionRef, memberCoordinate, methodCoordinate, propertyCoordinate, } from './coordinate.js';
4
+ export function schemaIdentity(schema) {
5
+ return Object.freeze({ origin: schema.origin, revision: revisionOfDomainSchema(schema) });
6
+ }
7
+ export function usageOf(schema, relation, key) {
8
+ return Object.freeze({
9
+ source: schemaIdentity(schema),
10
+ relation,
11
+ ...(key === undefined ? {} : { key }),
12
+ });
13
+ }
14
+ function collector(schema, result, key) {
15
+ return (ref, relation) => result.push({
16
+ coordinate: ref.kind === 'class'
17
+ ? classCoordinate(ref)
18
+ : memberCoordinate(ref),
19
+ usage: usageOf(schema, relation, key),
20
+ });
21
+ }
22
+ /** References keep their declaring member and relation; no strongest-usage collapse happens here. */
23
+ export function rootReferences(schema) {
24
+ const result = [];
25
+ for (const declaration of Object.values(schema.classes)) {
26
+ const key = keyForDefinitionRef(declaration.ref);
27
+ const add = collector(schema, result, key);
28
+ declaration.extends.forEach((ref) => add(ref, 'extends'));
29
+ for (const [name, field] of Object.entries(declaration.properties)) {
30
+ collectValueDefinitionRefs(field.schema, collector(schema, result, propertyCoordinate(declaration.ref, name).key));
31
+ }
32
+ collectClassPolicyRefs(declaration, add);
33
+ if (declaration.kind === 'edge')
34
+ collectEndpointRefs(declaration, add);
35
+ else
36
+ for (const [name, method] of Object.entries(declaration.methods)) {
37
+ collectCallableRefs(method, collector(schema, result, methodCoordinate(declaration.ref, name).key));
38
+ }
39
+ }
40
+ for (const declaration of Object.values(schema.functions))
41
+ collectCallableRefs(declaration, collector(schema, result, keyForDefinitionRef(declaration.ref)));
42
+ for (const declaration of Object.values(schema.policies))
43
+ collectPolicyDefinitionRefs(declaration, collector(schema, result, keyForDefinitionRef(declaration.ref)));
44
+ for (const declaration of Object.values(schema.views))
45
+ if (declaration.target.kind === 'definition') {
46
+ const add = collector(schema, result, keyForDefinitionRef(declaration.ref));
47
+ declaration.target.definitions.forEach((ref) => add(ref, 'view'));
48
+ }
49
+ collectCoreRefs(schema, collector(schema, result));
50
+ return result;
51
+ }
52
+ /** Existing DSL collectors own reference syntax. These functions attach comparison provenance. */
53
+ export function valueReferences(subject) {
54
+ const result = [];
55
+ collectValueDefinitionRefs(subject.field.schema, collector(subject.schema, result, subject.coordinate.key));
56
+ return result;
57
+ }
58
+ export function callableReferences(subject) {
59
+ const result = [];
60
+ collectCallableContractRefs(subject.method.declaration, collector(subject.schema, result, subject.coordinate.key));
61
+ return result;
62
+ }
63
+ export function classReferences(subject, inherit) {
64
+ const result = [];
65
+ const add = collector(subject.schema, result, subject.coordinate.key);
66
+ if (inherit) {
67
+ subject.analysis.declaration.extends.forEach((ref) => add(ref, 'extends'));
68
+ if (subject.analysis.declaration.kind === 'edge')
69
+ collectEndpointRefs(subject.analysis.declaration, add);
70
+ }
71
+ else {
72
+ for (const field of subject.analysis.properties.values())
73
+ result.push({
74
+ coordinate: propertyCoordinate(field.owner, field.name),
75
+ usage: usageOf(subject.schema, 'member', subject.coordinate.key),
76
+ });
77
+ for (const method of subject.analysis.methods.values())
78
+ result.push({
79
+ coordinate: methodCoordinate(method.owner, method.name),
80
+ usage: usageOf(subject.schema, 'member', subject.coordinate.key),
81
+ });
82
+ collectClassPolicyRefs(subject.analysis.declaration, add);
83
+ }
84
+ return result;
85
+ }
86
+ export function memberReferences(subject) {
87
+ const result = [];
88
+ const { schema, coordinate: { ref, key }, } = subject;
89
+ const add = collector(schema, result, key);
90
+ switch (ref.kind) {
91
+ case 'function':
92
+ collectCallableRefs(schema.functions[ref.name], add);
93
+ break;
94
+ case 'policy':
95
+ collectPolicyDefinitionRefs(schema.policies[ref.name], add);
96
+ break;
97
+ case 'view': {
98
+ const view = schema.views[ref.name];
99
+ if (view.target.kind === 'definition')
100
+ view.target.definitions.forEach((ref) => add(ref, 'view'));
101
+ break;
102
+ }
103
+ case 'core': {
104
+ add(schema.core.nodes[ref.name].class, 'endpoint');
105
+ for (const edge of schema.core.edges) {
106
+ if (!((edge.source.kind === 'core' && edge.source.name === ref.name) ||
107
+ (edge.target.kind === 'core' && edge.target.name === ref.name)))
108
+ continue;
109
+ add(edge.class, 'endpoint');
110
+ for (const endpoint of [edge.source, edge.target]) {
111
+ const ref = coreEndpointDefinitionRef(endpoint);
112
+ if (ref !== undefined)
113
+ add(ref, 'endpoint');
114
+ }
115
+ }
116
+ }
117
+ }
118
+ return result;
119
+ }
120
+ export function inherits(usage) {
121
+ return usage.relation === 'extends' || usage.relation === 'core';
122
+ }
@@ -0,0 +1,26 @@
1
+ import type { Value } from '@astrale-os/kernel-dsl/value';
2
+ import type { Assessment, ComparisonEntry, Facet } from './model.js';
3
+ import type { Reference } from './references.js';
4
+ import type { SubjectPair } from './scope.js';
5
+ export interface ComparisonFact {
6
+ readonly pair: SubjectPair;
7
+ readonly facet: Facet;
8
+ readonly before?: Value;
9
+ readonly after?: Value;
10
+ readonly requirement?: {
11
+ readonly kind: Assessment['requirement']['kind'];
12
+ readonly satisfied?: boolean;
13
+ };
14
+ }
15
+ /** Mutable collection is local to one analysis; callers only receive frozen completed reports. */
16
+ export declare function createReport(): {
17
+ note({ coordinate, usage }: Reference): void;
18
+ record(fact: ComparisonFact): void;
19
+ finish(): Readonly<{
20
+ entries: readonly ComparisonEntry[];
21
+ assessments: readonly Assessment[];
22
+ }>;
23
+ };
24
+ export type Report = ReturnType<typeof createReport>;
25
+ /** Full comparison merges facts; it never reclassifies the two analyses' conclusions. */
26
+ export declare function mergeEntries(...groups: readonly (readonly ComparisonEntry[])[]): readonly ComparisonEntry[];
@@ -0,0 +1,100 @@
1
+ import { json } from '@astrale-os/kernel-dsl/value';
2
+ import { compareUnicode } from '../../addressing/ordering.js';
3
+ import { deepFreeze } from '../canonical/freeze.js';
4
+ import { fingerprintMeaning } from './comparators/values.js';
5
+ import { differences } from './diff.js';
6
+ import { schemaIdentity } from './references.js';
7
+ import { identityOf } from './subjects.js';
8
+ /** Mutable collection is local to one analysis; callers only receive frozen completed reports. */
9
+ export function createReport() {
10
+ const usages = new Map();
11
+ const entries = new Map();
12
+ const assessments = new Map();
13
+ return {
14
+ note({ coordinate, usage }) {
15
+ const values = usages.get(coordinate.key) ?? [];
16
+ values.push(usage);
17
+ usages.set(coordinate.key, values);
18
+ },
19
+ record(fact) {
20
+ const { pair, facet, before, after } = fact;
21
+ const id = `${pair.coordinate.key}#${facet}`;
22
+ const entry = entries.get(pair.coordinate.key) ?? {
23
+ pair,
24
+ observations: new Map(),
25
+ };
26
+ const observation = entry.observations.get(id) ??
27
+ deepFreeze({
28
+ id,
29
+ facet,
30
+ state: before === undefined
31
+ ? 'added'
32
+ : after === undefined
33
+ ? 'missing'
34
+ : fingerprintMeaning(before) === fingerprintMeaning(after)
35
+ ? 'equal'
36
+ : 'changed',
37
+ ...(before === undefined ? {} : { before }),
38
+ ...(after === undefined ? {} : { after }),
39
+ differences: before === undefined || after === undefined ? [] : differences(before, after),
40
+ });
41
+ entry.observations.set(id, observation);
42
+ entries.set(pair.coordinate.key, entry);
43
+ if (fact.requirement !== undefined) {
44
+ assessments.set(`${id}:${fact.requirement.kind}`, {
45
+ pair,
46
+ requirement: fact.requirement,
47
+ observation,
48
+ });
49
+ }
50
+ },
51
+ finish() {
52
+ const completed = [...entries.values()]
53
+ .map(({ pair, observations }) => Object.freeze({
54
+ subject: identityOf(pair.coordinate),
55
+ ...(pair.source === undefined ? {} : { source: schemaIdentity(pair.source.schema) }),
56
+ ...(pair.target === undefined ? {} : { target: schemaIdentity(pair.target.schema) }),
57
+ usages: uniqueUsages(usages.get(pair.coordinate.key) ?? []),
58
+ observations: Object.freeze([...observations.values()].sort((a, b) => compareUnicode(a.id, b.id))),
59
+ }))
60
+ .sort((a, b) => compareUnicode(a.subject.key, b.subject.key));
61
+ return Object.freeze({
62
+ entries: Object.freeze(completed),
63
+ assessments: Object.freeze([...assessments.values()]
64
+ .map(({ pair, requirement, observation }) => deepFreeze({
65
+ subject: identityOf(pair.coordinate),
66
+ observation,
67
+ requirement: { kind: requirement.kind },
68
+ satisfied: requirement.satisfied ?? observation.state === 'equal',
69
+ }))
70
+ .sort((a, b) => compareUnicode(a.observation.id, b.observation.id) ||
71
+ compareUnicode(a.requirement.kind, b.requirement.kind))),
72
+ });
73
+ },
74
+ };
75
+ }
76
+ /** Full comparison merges facts; it never reclassifies the two analyses' conclusions. */
77
+ export function mergeEntries(...groups) {
78
+ const merged = new Map();
79
+ for (const entry of groups.flat()) {
80
+ const previous = merged.get(entry.subject.key);
81
+ merged.set(entry.subject.key, previous === undefined
82
+ ? entry
83
+ : Object.freeze({
84
+ ...entry,
85
+ usages: uniqueUsages([...previous.usages, ...entry.usages]),
86
+ observations: Object.freeze([
87
+ ...new Map([...previous.observations, ...entry.observations].map((value) => [
88
+ value.id,
89
+ value,
90
+ ])).values(),
91
+ ].sort((a, b) => compareUnicode(a.id, b.id))),
92
+ }));
93
+ }
94
+ return Object.freeze([...merged.values()].sort((a, b) => compareUnicode(a.subject.key, b.subject.key)));
95
+ }
96
+ function uniqueUsages(values) {
97
+ return Object.freeze([...new Map(values.map((value) => [json.serialize(value), value])).entries()]
98
+ .sort(([a], [b]) => compareUnicode(a, b))
99
+ .map(([, value]) => value));
100
+ }
@@ -0,0 +1,27 @@
1
+ import type { Key } from '../../addressing/index.js';
2
+ import type { DomainSchema } from '../model/schema.js';
3
+ import type { Context } from './context.js';
4
+ import type { Coordinate } from './coordinate.js';
5
+ import type { ComparisonRequest, ComparisonScope } from './model.js';
6
+ import type { Reference } from './references.js';
7
+ import type { ResolvedSubject } from './subjects.js';
8
+ export interface SubjectPair {
9
+ readonly coordinate: Coordinate;
10
+ readonly source: ResolvedSubject | undefined;
11
+ readonly target: ResolvedSubject | undefined;
12
+ }
13
+ export interface ComparisonContext {
14
+ readonly scope: ComparisonScope;
15
+ readonly source: DomainSchema;
16
+ readonly target: DomainSchema;
17
+ readonly dependent?: DomainSchema;
18
+ readonly before: Context;
19
+ readonly after: Context;
20
+ readonly roots: readonly Reference[];
21
+ pair(coordinate: Coordinate): SubjectPair;
22
+ }
23
+ export declare function prepare(request: ComparisonRequest): ComparisonContext;
24
+ export declare function retainedContext(dependent: DomainSchema, origin: Key.Origin, path: '/target/origin' | '/origin'): {
25
+ source: DomainSchema;
26
+ context: Context;
27
+ };
@@ -0,0 +1,78 @@
1
+ import { compareUnicode } from '../../addressing/ordering.js';
2
+ import { SchemaError, diagnostic } from '../diagnostic.js';
3
+ import { knownDomainClosure } from '../resolution/closure.js';
4
+ import { resolve } from '../resolution/domain.js';
5
+ import { contextOf } from './context.js';
6
+ import { rootReferences, schemaIdentity, usageOf } from './references.js';
7
+ import { coordinateForKey, resolveSubject } from './subjects.js';
8
+ export function prepare(request) {
9
+ const { target } = request;
10
+ let source;
11
+ let before;
12
+ let roots;
13
+ const dependent = request.scope.kind === 'dependency' ? request.scope.dependent : undefined;
14
+ if (request.scope.kind === 'dependency') {
15
+ const retained = retainedContext(request.scope.dependent, target.origin, '/target/origin');
16
+ source = retained.source;
17
+ before = retained.context;
18
+ roots = rootReferences(request.scope.dependent);
19
+ }
20
+ else {
21
+ source = request.scope.source;
22
+ if (source.origin !== target.origin)
23
+ throw new SchemaError([
24
+ diagnostic('DM_MEMBER_ORIGIN_MISMATCH', '/target/origin', `Member comparison target ${target.origin} does not match source ${source.origin}.`),
25
+ ]);
26
+ const keys = request.scope.roots;
27
+ if (!Array.isArray(keys) || keys.length === 0 || new Set(keys).size !== keys.length) {
28
+ throw new SchemaError([
29
+ diagnostic('DM_MEMBER_ROOT_INVALID', '/roots', 'Member comparison roots must be one non-empty duplicate-free Key set.'),
30
+ ]);
31
+ }
32
+ resolve(source);
33
+ before = contextOf(source);
34
+ roots = keys.map((key, index) => ({
35
+ coordinate: coordinateForKey(before, source, key, index),
36
+ usage: usageOf(source, 'selection', key),
37
+ }));
38
+ }
39
+ resolve(target);
40
+ const after = contextOf(target);
41
+ const pairs = new Map();
42
+ return {
43
+ source,
44
+ target,
45
+ dependent,
46
+ before,
47
+ after,
48
+ roots,
49
+ scope: Object.freeze({
50
+ kind: request.scope.kind,
51
+ source: schemaIdentity(source),
52
+ target: schemaIdentity(target),
53
+ ...(dependent === undefined ? {} : { dependent: schemaIdentity(dependent) }),
54
+ roots: Object.freeze([...new Set(roots.map(({ coordinate }) => coordinate.key))].sort(compareUnicode)),
55
+ }),
56
+ pair(coordinate) {
57
+ let pair = pairs.get(coordinate.key);
58
+ if (pair === undefined) {
59
+ pair = {
60
+ coordinate,
61
+ source: resolveSubject({ context: before, coordinate }),
62
+ target: resolveSubject({ context: after, coordinate }),
63
+ };
64
+ pairs.set(coordinate.key, pair);
65
+ }
66
+ return pair;
67
+ },
68
+ };
69
+ }
70
+ export function retainedContext(dependent, origin, path) {
71
+ resolve(dependent);
72
+ const source = (knownDomainClosure(dependent) ?? []).find((entry) => entry.schema.origin === origin)?.schema;
73
+ if (source === undefined)
74
+ throw new SchemaError([
75
+ diagnostic('DM_DEPENDENCY_NOT_RETAINED', path, `Domain ${dependent.origin} was not admitted with a dependency at ${origin}.`),
76
+ ]);
77
+ return { source, context: contextOf(dependent) };
78
+ }
@@ -0,0 +1,3 @@
1
+ import type { StructureComparison } from '../model.js';
2
+ import type { ComparisonContext } from '../scope.js';
3
+ export declare function analyzeStructure(context: ComparisonContext): StructureComparison;
@@ -0,0 +1,16 @@
1
+ import { evidenceOf } from '../evidence.js';
2
+ import { createReport } from '../report.js';
3
+ import { compareInheritance } from './inheritance.js';
4
+ import { compareRequirements } from './requirements.js';
5
+ export function analyzeStructure(context) {
6
+ const report = createReport();
7
+ compareRequirements({ context, report });
8
+ compareInheritance({ context, report });
9
+ const result = { scope: context.scope, ...report.finish() };
10
+ return Object.freeze({
11
+ kind: 'structure',
12
+ ...result,
13
+ compatible: result.assessments.every(({ satisfied }) => satisfied),
14
+ evidence: evidenceOf({ kind: 'structure', ...result }),
15
+ });
16
+ }
@@ -0,0 +1,7 @@
1
+ import type { Report } from '../report.js';
2
+ import type { ComparisonContext } from '../scope.js';
3
+ /** Recompose retained descendants using the same obligations as source and compiled admission. */
4
+ export declare function compareInheritance({ context, report, }: {
5
+ readonly context: ComparisonContext;
6
+ readonly report: Report;
7
+ }): void;
@@ -0,0 +1,68 @@
1
+ import { json } from '@astrale-os/kernel-dsl/value';
2
+ import { compareUnicode, definitionRefIndex } from '../../../addressing/ordering.js';
3
+ import { composeMethods } from '../../../language/callable/composition.js';
4
+ import { sameMethodSignature } from '../../analysis/class.js';
5
+ import { methodInheritance } from '../comparators/callables.js';
6
+ import { effectiveDefinition, requireOwner } from '../context.js';
7
+ import { classCoordinate, keyForDefinitionRef } from '../coordinate.js';
8
+ import { inherits, usageOf } from '../references.js';
9
+ import { resolveSubject } from '../subjects.js';
10
+ /** Recompose retained descendants using the same obligations as source and compiled admission. */
11
+ export function compareInheritance({ context, report, }) {
12
+ if (context.dependent === undefined)
13
+ return;
14
+ const memo = new Map();
15
+ const visit = (ref) => {
16
+ const index = definitionRefIndex(ref);
17
+ const retained = memo.get(index);
18
+ if (retained !== undefined)
19
+ return retained;
20
+ const owner = requireOwner(context.before, ref, keyForDefinitionRef(ref));
21
+ const original = effectiveDefinition(context.before, owner, ref);
22
+ if (ref.origin === context.target.origin) {
23
+ const replacement = effectiveDefinition(context.after, context.target, ref);
24
+ return { composition: replacement ?? original, affected: replacement !== undefined };
25
+ }
26
+ const parents = original.declaration.extends.map(visit);
27
+ if (!parents.some((parent) => parent.affected)) {
28
+ const unchanged = { composition: original, affected: false };
29
+ memo.set(index, unchanged);
30
+ return unchanged;
31
+ }
32
+ const declaration = original.declaration;
33
+ const own = new Map(Object.keys(declaration.kind === 'node' ? declaration.methods : {}).map((name) => [
34
+ name,
35
+ original.methods.get(name),
36
+ ]));
37
+ const issues = [];
38
+ const composition = composeMethods(parents.map((parent) => parent.composition), own, declaration.abstract, (left, right) => sameMethodSignature(left.declaration, right.declaration), (_message, issue) => issues.push(issue));
39
+ const coordinate = classCoordinate(ref);
40
+ const subject = resolveSubject({ context: context.before, coordinate });
41
+ report.note({ coordinate, usage: usageOf(owner, 'extends', coordinate.key) });
42
+ report.record({
43
+ pair: { coordinate, source: subject, target: subject },
44
+ facet: 'class.composition',
45
+ before: compositionValue(original, []),
46
+ after: compositionValue(composition, issues),
47
+ requirement: { kind: 'class.composition', satisfied: issues.length === 0 },
48
+ });
49
+ const result = { composition, affected: true };
50
+ memo.set(index, result);
51
+ return result;
52
+ };
53
+ for (const declaration of Object.values(context.dependent.classes))
54
+ visit(declaration.ref);
55
+ for (const root of context.roots) {
56
+ if (root.coordinate.kind === 'class' && inherits(root.usage))
57
+ visit(root.coordinate.ref);
58
+ }
59
+ }
60
+ function compositionValue(composition, issues) {
61
+ return {
62
+ requirements: [...composition.requirements.values()]
63
+ .sort((a, b) => compareUnicode(a.key, b.key))
64
+ .map(methodInheritance),
65
+ frontier: [...composition.frontier.keys()].sort(compareUnicode),
66
+ issues: [...issues].sort((a, b) => compareUnicode(json.serialize(a), json.serialize(b))),
67
+ };
68
+ }