@astrale-os/kernel-dsl 0.2.0-beta.37 → 0.2.0-beta.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/v1/builder/core/authoring.d.ts +7 -2
- package/dist/v1/builder/lowering/schema/context.d.ts +9 -4
- package/dist/v1/builder/lowering/schema/context.js +20 -20
- package/dist/v1/builder/policy/authoring.d.ts +6 -6
- package/dist/v1/builder/policy/authoring.js +3 -1
- package/dist/v1/compiled/admission/accept.js +97 -18
- package/dist/v1/compiled/admission/semantics.js +77 -24
- package/dist/v1/compiled/compiler/compile.js +23 -7
- package/dist/v1/compiled/loader/load.d.ts +11 -2
- package/dist/v1/compiled/loader/load.js +4 -1
- package/dist/v1/compiled/model/schema.d.ts +5 -4
- package/dist/v1/domain/create.js +17 -9
- package/dist/v1/index.d.ts +1 -1
- package/dist/v1/language/callable/composition.d.ts +13 -1
- package/dist/v1/language/callable/composition.js +13 -3
- package/dist/v1/language/core/model/core.d.ts +7 -1
- package/dist/v1/language/core/resolved.d.ts +6 -2
- package/dist/v1/language/index.d.ts +1 -1
- package/dist/v1/language/index.js +1 -1
- package/dist/v1/language/policy/index.d.ts +1 -1
- package/dist/v1/language/policy/index.js +1 -1
- package/dist/v1/language/policy/model/policy.d.ts +1 -0
- package/dist/v1/language/policy/model/policy.js +17 -0
- package/dist/v1/language/value/compile/fields.d.ts +1 -1
- package/dist/v1/language/value/compile/load.d.ts +4 -2
- package/dist/v1/language/value/compile/load.js +22 -1
- package/dist/v1/language/value/compile/model.d.ts +16 -1
- package/dist/v1/language/value/evaluate/assertions.d.ts +2 -0
- package/dist/v1/language/value/evaluate/assertions.js +13 -0
- package/dist/v1/language/value/evaluate/evaluator.js +78 -17
- package/dist/v1/language/value/evaluate/preparation.d.ts +10 -1
- package/dist/v1/language/value/evaluate/preparation.js +17 -26
- package/dist/v1/language/value/evaluate/program.js +1 -1
- package/dist/v1/language/value/evaluate/validate.d.ts +6 -2
- package/dist/v1/language/value/evaluate/validate.js +18 -11
- package/dist/v1/language/value/ordering.d.ts +2 -0
- package/dist/v1/language/value/ordering.js +4 -8
- package/dist/v1/language/value/regexp/compile.d.ts +3 -3
- package/dist/v1/language/value/regexp/compile.js +56 -11
- package/dist/v1/language/value/regexp/matcher.d.ts +3 -3
- package/dist/v1/language/value/regexp/matcher.js +75 -3
- package/dist/v1/schema/analysis/class.d.ts +21 -0
- package/dist/v1/schema/analysis/class.js +18 -9
- package/dist/v1/schema/compatibility/api.d.ts +2 -0
- package/dist/v1/schema/compatibility/api.js +1 -0
- package/dist/v1/schema/compatibility/comparators/callables.d.ts +28 -0
- package/dist/v1/schema/compatibility/comparators/callables.js +77 -0
- package/dist/v1/schema/compatibility/comparators/classes.d.ts +28 -0
- package/dist/v1/schema/compatibility/comparators/classes.js +96 -0
- package/dist/v1/schema/compatibility/comparators/index.d.ts +12 -0
- package/dist/v1/schema/compatibility/comparators/index.js +41 -0
- package/dist/v1/schema/compatibility/comparators/members.d.ts +4 -0
- package/dist/v1/schema/compatibility/comparators/members.js +49 -0
- package/dist/v1/schema/compatibility/comparators/values.d.ts +5 -0
- package/dist/v1/schema/compatibility/comparators/values.js +37 -0
- package/dist/v1/schema/compatibility/compare.d.ts +5 -57
- package/dist/v1/schema/compatibility/compare.js +19 -162
- package/dist/v1/schema/compatibility/coordinate.d.ts +2 -7
- package/dist/v1/schema/compatibility/coordinate.js +2 -6
- package/dist/v1/schema/compatibility/evidence.d.ts +5 -0
- package/dist/v1/schema/compatibility/evidence.js +51 -0
- package/dist/v1/schema/compatibility/fields.d.ts +6 -0
- package/dist/v1/schema/compatibility/fields.js +5 -0
- package/dist/v1/schema/compatibility/index.d.ts +5 -4
- package/dist/v1/schema/compatibility/index.js +3 -2
- package/dist/v1/schema/compatibility/legacy/index.d.ts +58 -0
- package/dist/v1/schema/compatibility/legacy/index.js +145 -0
- package/dist/v1/schema/compatibility/meaning/footprint.d.ts +7 -0
- package/dist/v1/schema/compatibility/meaning/footprint.js +57 -0
- package/dist/v1/schema/compatibility/meaning/index.d.ts +3 -0
- package/dist/v1/schema/compatibility/meaning/index.js +14 -0
- package/dist/v1/schema/compatibility/model.d.ts +96 -0
- package/dist/v1/schema/compatibility/model.js +1 -0
- package/dist/v1/schema/compatibility/references.d.ts +30 -0
- package/dist/v1/schema/compatibility/references.js +122 -0
- package/dist/v1/schema/compatibility/report.d.ts +26 -0
- package/dist/v1/schema/compatibility/report.js +100 -0
- package/dist/v1/schema/compatibility/scope.d.ts +27 -0
- package/dist/v1/schema/compatibility/scope.js +78 -0
- package/dist/v1/schema/compatibility/structure/index.d.ts +3 -0
- package/dist/v1/schema/compatibility/structure/index.js +16 -0
- package/dist/v1/schema/compatibility/structure/inheritance.d.ts +7 -0
- package/dist/v1/schema/compatibility/structure/inheritance.js +68 -0
- package/dist/v1/schema/compatibility/structure/requirements.d.ts +7 -0
- package/dist/v1/schema/compatibility/structure/requirements.js +70 -0
- package/dist/v1/schema/compatibility/subjects.d.ts +34 -0
- package/dist/v1/schema/compatibility/subjects.js +86 -0
- package/dist/v1/schema/fingerprint.d.ts +2 -2
- package/dist/v1/schema/fingerprint.js +1 -1
- package/dist/v1/schema/index.d.ts +3 -0
- package/dist/v1/schema/index.js +2 -0
- package/dist/v1/schema/normalization/domain.js +15 -1
- package/dist/v1/schema/resolution/domain.js +17 -10
- package/dist/v1/schema/resolution/origins.d.ts +3 -0
- package/dist/v1/schema/resolution/origins.js +12 -4
- package/dist/v1/schema/validation/core.js +78 -26
- package/dist/v1/schema/validation/domain.structure.js +7 -2
- package/package.json +2 -2
- package/dist/v1/schema/compatibility/footprint.d.ts +0 -25
- package/dist/v1/schema/compatibility/footprint.js +0 -263
- package/dist/v1/schema/compatibility/meaning.d.ts +0 -44
- package/dist/v1/schema/compatibility/meaning.js +0 -234
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Report } from '../report.js';
|
|
2
|
+
import type { ComparisonContext } from '../scope.js';
|
|
3
|
+
/** Structure follows data and abstract obligations; concrete IO never expands its requirements. */
|
|
4
|
+
export declare function compareRequirements({ context, report, }: {
|
|
5
|
+
readonly context: ComparisonContext;
|
|
6
|
+
readonly report: Report;
|
|
7
|
+
}): void;
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { methodCoordinate } from '../coordinate.js';
|
|
6
|
+
import { callableReferences, classReferences, inherits, memberReferences, usageOf, valueReferences, } from '../references.js';
|
|
7
|
+
/** Structure follows data and abstract obligations; concrete IO never expands its requirements. */
|
|
8
|
+
export function compareRequirements({ context, report, }) {
|
|
9
|
+
const queue = [...context.roots];
|
|
10
|
+
const visited = new Set();
|
|
11
|
+
for (const reference of queue) {
|
|
12
|
+
report.note(reference);
|
|
13
|
+
const { coordinate, usage } = reference;
|
|
14
|
+
const visit = `${coordinate.key}:${usage.relation}`;
|
|
15
|
+
if (visited.has(visit))
|
|
16
|
+
continue;
|
|
17
|
+
visited.add(visit);
|
|
18
|
+
const pair = context.pair(coordinate);
|
|
19
|
+
const subject = pair.source;
|
|
20
|
+
if (subject === undefined)
|
|
21
|
+
throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
|
|
22
|
+
let facet;
|
|
23
|
+
switch (subject.kind) {
|
|
24
|
+
case 'class': {
|
|
25
|
+
facet = inherits(usage) ? 'class.inherit' : 'class.reference';
|
|
26
|
+
queue.push(...classReferences(subject, false));
|
|
27
|
+
if (inherits(usage)) {
|
|
28
|
+
queue.push(...classReferences(subject, true));
|
|
29
|
+
for (const method of subject.analysis.requirements.values())
|
|
30
|
+
queue.push({
|
|
31
|
+
coordinate: methodCoordinate(method.owner, method.name),
|
|
32
|
+
usage: usageOf(subject.schema, 'abstract', coordinate.key),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case 'property':
|
|
38
|
+
facet = 'property';
|
|
39
|
+
queue.push(...valueReferences(subject));
|
|
40
|
+
break;
|
|
41
|
+
case 'method': {
|
|
42
|
+
facet = usage.relation === 'abstract' ? 'method.inheritance' : 'method.existence';
|
|
43
|
+
if (facet === 'method.inheritance')
|
|
44
|
+
queue.push(...callableReferences(subject));
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'member': {
|
|
48
|
+
facet =
|
|
49
|
+
subject.coordinate.ref.kind === 'function'
|
|
50
|
+
? 'function.existence'
|
|
51
|
+
: subject.coordinate.ref.kind;
|
|
52
|
+
if (facet !== 'function.existence')
|
|
53
|
+
queue.push(...memberReferences(subject));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (Key.origin(coordinate.key) !== context.source.origin)
|
|
57
|
+
continue;
|
|
58
|
+
// Class membership does not establish an invocation dependency. Explicit member selection
|
|
59
|
+
// and inherited abstract obligations do establish requirements; call tracking can add others.
|
|
60
|
+
const required = facet !== 'method.existence' || usage.relation === 'selection';
|
|
61
|
+
report.record({
|
|
62
|
+
pair,
|
|
63
|
+
facet,
|
|
64
|
+
...compareFacet({ context, pair, facet }),
|
|
65
|
+
...(required ? { requirement: { kind: facet } } : {}),
|
|
66
|
+
});
|
|
67
|
+
if (facet === 'class.inherit')
|
|
68
|
+
compareAddedProperties({ context, pair, report });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Key as SemanticKey } from '../../addressing/index.js';
|
|
2
|
+
import type { DefinitionAnalysis, EffectiveField, EffectiveMethod } from '../analysis/class.js';
|
|
3
|
+
import type { DomainSchema } from '../model/schema.js';
|
|
4
|
+
import type { Context } from './context.js';
|
|
5
|
+
import type { ClassCoordinate, Coordinate, MemberCoordinate, MethodCoordinate, PropertyCoordinate } from './coordinate.js';
|
|
6
|
+
import type { Subject } from './model.js';
|
|
7
|
+
export type ResolvedSubject = {
|
|
8
|
+
readonly kind: 'class';
|
|
9
|
+
readonly coordinate: ClassCoordinate;
|
|
10
|
+
readonly schema: DomainSchema;
|
|
11
|
+
readonly analysis: DefinitionAnalysis;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'property';
|
|
14
|
+
readonly coordinate: PropertyCoordinate;
|
|
15
|
+
readonly schema: DomainSchema;
|
|
16
|
+
readonly field: EffectiveField;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: 'method';
|
|
19
|
+
readonly coordinate: MethodCoordinate;
|
|
20
|
+
readonly schema: DomainSchema;
|
|
21
|
+
readonly method: EffectiveMethod;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'member';
|
|
24
|
+
readonly coordinate: MemberCoordinate;
|
|
25
|
+
readonly schema: DomainSchema;
|
|
26
|
+
};
|
|
27
|
+
/** Resolve a trusted coordinate within one exact accepted closure. */
|
|
28
|
+
export declare function resolveSubject({ context, coordinate, }: {
|
|
29
|
+
readonly context: Context;
|
|
30
|
+
readonly coordinate: Coordinate;
|
|
31
|
+
}): ResolvedSubject | undefined;
|
|
32
|
+
export declare function identityOf(coordinate: Coordinate): Subject;
|
|
33
|
+
/** Resolve one caller-supplied Key of the source into an exact coordinate that exists there. */
|
|
34
|
+
export declare function coordinateForKey(context: Context, source: DomainSchema, input: SemanticKey, index: number): Coordinate;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Key } from '../../addressing/index.js';
|
|
2
|
+
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
3
|
+
import { effectiveDefinition, effectiveMethod } from './context.js';
|
|
4
|
+
import { classCoordinate, keyReference, memberCoordinate, methodCoordinate, propertyCoordinate, } from './coordinate.js';
|
|
5
|
+
/** Resolve a trusted coordinate within one exact accepted closure. */
|
|
6
|
+
export function resolveSubject({ context, coordinate, }) {
|
|
7
|
+
const schema = context.schemas.get(Key.origin(coordinate.key));
|
|
8
|
+
if (schema === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (coordinate.kind === 'member') {
|
|
11
|
+
const { ref } = coordinate;
|
|
12
|
+
const declaration = ref.kind === 'function'
|
|
13
|
+
? schema.functions[ref.name]
|
|
14
|
+
: ref.kind === 'policy'
|
|
15
|
+
? schema.policies[ref.name]
|
|
16
|
+
: ref.kind === 'view'
|
|
17
|
+
? schema.views[ref.name]
|
|
18
|
+
: schema.core.nodes[ref.name];
|
|
19
|
+
return declaration === undefined ? undefined : { kind: 'member', coordinate, schema };
|
|
20
|
+
}
|
|
21
|
+
const analysis = effectiveDefinition(context, schema, coordinate.kind === 'class' ? coordinate.ref : coordinate.owner);
|
|
22
|
+
if (analysis === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (coordinate.kind === 'class')
|
|
25
|
+
return { kind: 'class', coordinate, schema, analysis };
|
|
26
|
+
if (coordinate.kind === 'property') {
|
|
27
|
+
const field = analysis.properties.get(coordinate.key);
|
|
28
|
+
return field === undefined ? undefined : { kind: 'property', coordinate, schema, field };
|
|
29
|
+
}
|
|
30
|
+
const method = effectiveMethod(analysis, coordinate.key);
|
|
31
|
+
return method === undefined ? undefined : { kind: 'method', coordinate, schema, method };
|
|
32
|
+
}
|
|
33
|
+
export function identityOf(coordinate) {
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
key: coordinate.key,
|
|
36
|
+
kind: coordinate.kind === 'member' ? coordinate.ref.kind : coordinate.kind,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Resolve one caller-supplied Key of the source into an exact coordinate that exists there. */
|
|
40
|
+
export function coordinateForKey(context, source, input, index) {
|
|
41
|
+
let key;
|
|
42
|
+
try {
|
|
43
|
+
key = Key(input);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return invalidMemberRoot(input, index, 'is not a canonical semantic Key');
|
|
47
|
+
}
|
|
48
|
+
if (Key.origin(key) !== source.origin) {
|
|
49
|
+
return invalidMemberRoot(key, index, `belongs to ${Key.origin(key)}, not source ${source.origin}`);
|
|
50
|
+
}
|
|
51
|
+
const reference = keyReference(key);
|
|
52
|
+
let coordinate;
|
|
53
|
+
if (reference.member !== undefined && reference.ownerName !== undefined) {
|
|
54
|
+
if (source.classes[reference.ownerName] === undefined) {
|
|
55
|
+
return invalidMemberRoot(key, index, 'has no exact owning Definition');
|
|
56
|
+
}
|
|
57
|
+
const owner = { origin: source.origin, kind: 'class', name: reference.ownerName };
|
|
58
|
+
coordinate =
|
|
59
|
+
reference.member === 'property'
|
|
60
|
+
? propertyCoordinate(owner, reference.name)
|
|
61
|
+
: methodCoordinate(owner, reference.name);
|
|
62
|
+
}
|
|
63
|
+
else if (reference.kind === 'class') {
|
|
64
|
+
if (source.classes[reference.name] === undefined) {
|
|
65
|
+
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
66
|
+
}
|
|
67
|
+
// Explicit Class selection compares its surface without inferring inheritance.
|
|
68
|
+
coordinate = classCoordinate({ origin: source.origin, kind: 'class', name: reference.name });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
coordinate = memberCoordinate({
|
|
72
|
+
origin: source.origin,
|
|
73
|
+
kind: reference.kind,
|
|
74
|
+
name: reference.name,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (resolveSubject({ context, coordinate }) === undefined) {
|
|
78
|
+
return invalidMemberRoot(key, index, 'does not exist in the source');
|
|
79
|
+
}
|
|
80
|
+
return coordinate;
|
|
81
|
+
}
|
|
82
|
+
function invalidMemberRoot(key, index, reason) {
|
|
83
|
+
throw new SchemaError([
|
|
84
|
+
diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Key ${JSON.stringify(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { callableFingerprint } from './compatibility/
|
|
2
|
-
export type { MeaningFingerprint } from './compatibility/
|
|
1
|
+
export { callableFingerprint } from './compatibility/comparators/callables.js';
|
|
2
|
+
export type { Fingerprint as MeaningFingerprint } from './compatibility/model.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { callableFingerprint } from './compatibility/
|
|
1
|
+
export { callableFingerprint } from './compatibility/comparators/callables.js';
|
|
@@ -13,6 +13,7 @@ export { decodeDomainSchema as decode, encodeDomainSchema as encode, serializeDo
|
|
|
13
13
|
export { revisionOfDomainSchema as revision } from './canonical/revision.js';
|
|
14
14
|
export { ANNOTATION_KEYWORDS, FIELD_ROLES, callableFingerprint, compareDependencyMeaning, compareMemberMeaning, describeMeaningChange, footprintOf, meaningOf, } from './compatibility/index.js';
|
|
15
15
|
export type { DependencyMeaningChange, DependencyMeaningChangeKind, DependencyMeaningComparison, DependencyMeaningLevel, FieldRole, FootprintEntry, MeaningDifference, MeaningFingerprint, } from './compatibility/index.js';
|
|
16
|
+
export * as compatibility from './compatibility/api.js';
|
|
16
17
|
export { header } from './codec/header.js';
|
|
17
18
|
export { DOMAIN_FORMAT as FORMAT, DOMAIN_VERSION as VERSION } from './model/format.js';
|
|
18
19
|
export { compile, load } from '../compiled/index.js';
|
|
@@ -24,3 +25,5 @@ export type { DomainSchemaHeader as Header } from './codec/header.js';
|
|
|
24
25
|
export type { DomainDependency } from '../addressing/index.js';
|
|
25
26
|
/** The versioned value-schema grammar embedded in declarations. */
|
|
26
27
|
export * as values from './validation/value-api.js';
|
|
28
|
+
export { compareDependencyStructure } from './compatibility/index.js';
|
|
29
|
+
export type { DependencyStructureComparison } from './compatibility/index.js';
|
package/dist/v1/schema/index.js
CHANGED
|
@@ -5,9 +5,11 @@ 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
7
|
export { ANNOTATION_KEYWORDS, FIELD_ROLES, callableFingerprint, compareDependencyMeaning, compareMemberMeaning, describeMeaningChange, footprintOf, meaningOf, } from './compatibility/index.js';
|
|
8
|
+
export * as compatibility from './compatibility/api.js';
|
|
8
9
|
export { header } from './codec/header.js';
|
|
9
10
|
export { DOMAIN_FORMAT as FORMAT, DOMAIN_VERSION as VERSION } from './model/format.js';
|
|
10
11
|
export { compile, load } from '../compiled/index.js';
|
|
11
12
|
export { SchemaError as Error } from './diagnostic.js';
|
|
12
13
|
/** The versioned value-schema grammar embedded in declarations. */
|
|
13
14
|
export * as values from './validation/value-api.js';
|
|
15
|
+
export { compareDependencyStructure } from './compatibility/index.js';
|
|
@@ -175,7 +175,21 @@ function compareCoreEndpoints(left, right) {
|
|
|
175
175
|
return right.kind === 'domain' ? 0 : -1;
|
|
176
176
|
if (right.kind === 'domain')
|
|
177
177
|
return 1;
|
|
178
|
-
|
|
178
|
+
const [a, b] = [coreEndpointOrder(left), coreEndpointOrder(right)];
|
|
179
|
+
return (compareUnicode(a.origin, b.origin) ||
|
|
180
|
+
compareUnicode(a.kind, b.kind) ||
|
|
181
|
+
compareUnicode(a.name, b.name) ||
|
|
182
|
+
compareUnicode(a.member, b.member));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A Method orders under its owner Class through the synthetic kind `method`; every other endpoint
|
|
186
|
+
* keeps the exact `compareDefinitionRefs` tuple with an empty member, so the canonical order of
|
|
187
|
+
* already-authored Core Edges — and therefore their revisions — never moves.
|
|
188
|
+
*/
|
|
189
|
+
function coreEndpointOrder(ref) {
|
|
190
|
+
return ref.kind === 'method'
|
|
191
|
+
? { origin: ref.owner.origin, kind: ref.kind, name: ref.owner.name, member: ref.name }
|
|
192
|
+
: { origin: ref.origin, kind: ref.kind, name: ref.name, member: '' };
|
|
179
193
|
}
|
|
180
194
|
function compareBytes(left, right) {
|
|
181
195
|
const encoder = new TextEncoder();
|
|
@@ -5,8 +5,8 @@ import { createDomainValidation, unknownInputField } from '../../domain/validati
|
|
|
5
5
|
import { inputFieldNodes } from '../../language/value/compile/fields.js';
|
|
6
6
|
import { prepareValueSchema } from '../../language/value/evaluate/preparation.js';
|
|
7
7
|
import { validateValueProgram } from '../../language/value/evaluate/program.js';
|
|
8
|
-
import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
|
|
9
|
-
import {
|
|
8
|
+
import { validateJsonInstance, validatePreparedInstance, } from '../../language/value/evaluate/validate.js';
|
|
9
|
+
import { classifyPreparedOrdering } from '../../language/value/ordering.js';
|
|
10
10
|
import { revisionOfDomainSchema } from '../canonical/revision.js';
|
|
11
11
|
import { SchemaError } from '../diagnostic.js';
|
|
12
12
|
import { validateDomainSchemaSemantics } from '../validation/domain.js';
|
|
@@ -51,15 +51,20 @@ function resolveWithLookup(schema, lookup) {
|
|
|
51
51
|
function sourceValidation(closure, registries) {
|
|
52
52
|
const properties = new Map();
|
|
53
53
|
const callables = new Map();
|
|
54
|
+
const patterns = new Map();
|
|
54
55
|
for (const owner of closure) {
|
|
55
56
|
const registry = registries.get(owner.origin);
|
|
56
57
|
if (registry === undefined)
|
|
57
58
|
throw new TypeError(`No Value registry exists for ${owner.origin}.`);
|
|
58
59
|
for (const definition of Object.values(owner.classes)) {
|
|
59
60
|
for (const [name, property] of Object.entries(definition.properties)) {
|
|
61
|
+
const prepared = prepareValueSchema(property.schema, registry, '', patterns);
|
|
62
|
+
if (!prepared.compiled) {
|
|
63
|
+
throw new TypeError(`Accepted Property cannot be prepared: ${prepared.message}`);
|
|
64
|
+
}
|
|
60
65
|
properties.set(PropertyKey.of(definition.ref, name), Object.freeze({
|
|
61
|
-
ordering:
|
|
62
|
-
validate: (input) =>
|
|
66
|
+
ordering: classifyPreparedOrdering(prepared.value),
|
|
67
|
+
validate: (input) => validatePreparedInstance(prepared.value, input),
|
|
63
68
|
}));
|
|
64
69
|
}
|
|
65
70
|
if (definition.kind !== 'node')
|
|
@@ -69,7 +74,7 @@ function sourceValidation(closure, registries) {
|
|
|
69
74
|
? undefined
|
|
70
75
|
: method.output.mode === 'value'
|
|
71
76
|
? method.output.schema
|
|
72
|
-
: method.output.item, registry));
|
|
77
|
+
: method.output.item, registry, patterns));
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
for (const definition of Object.values(owner.functions)) {
|
|
@@ -77,13 +82,13 @@ function sourceValidation(closure, registries) {
|
|
|
77
82
|
? undefined
|
|
78
83
|
: definition.output.mode === 'value'
|
|
79
84
|
? definition.output.schema
|
|
80
|
-
: definition.output.item, registry));
|
|
85
|
+
: definition.output.item, registry, patterns));
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
return createDomainValidation(properties, callables);
|
|
84
89
|
}
|
|
85
|
-
function callableValidation(input, output, registry) {
|
|
86
|
-
const prepared = prepareValueSchema(input, registry, '');
|
|
90
|
+
function callableValidation(input, output, registry, patterns) {
|
|
91
|
+
const prepared = prepareValueSchema(input, registry, '', patterns);
|
|
87
92
|
if (!prepared.compiled) {
|
|
88
93
|
throw new TypeError(`Accepted callable input cannot be prepared: ${prepared.message}`);
|
|
89
94
|
}
|
|
@@ -91,14 +96,16 @@ function callableValidation(input, output, registry) {
|
|
|
91
96
|
const fields = inputFieldNodes(inputProgram);
|
|
92
97
|
return Object.freeze({
|
|
93
98
|
...(fields === undefined ? {} : { fields: Object.freeze(Object.keys(fields)) }),
|
|
94
|
-
validateInput: (value) =>
|
|
99
|
+
validateInput: (value) => validatePreparedInstance(prepared.value, value),
|
|
95
100
|
validateInputField(field, value) {
|
|
96
101
|
const nodes = fields?.[field];
|
|
97
102
|
return nodes === undefined
|
|
98
103
|
? unknownInputField(field)
|
|
99
104
|
: validateValueProgram(inputProgram, value, nodes);
|
|
100
105
|
},
|
|
101
|
-
validateOutput: (value) => output === undefined
|
|
106
|
+
validateOutput: (value) => output === undefined
|
|
107
|
+
? []
|
|
108
|
+
: validateJsonInstance(output, value, registry, '', patterns),
|
|
102
109
|
});
|
|
103
110
|
}
|
|
104
111
|
function definitionsOf(schema) {
|