@astrale-os/kernel-dsl 1.0.0-beta.1 → 1.0.0-beta.11
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/addressing/class-key.js +3 -3
- package/dist/v1/addressing/key.d.ts +4 -1
- package/dist/v1/addressing/key.js +21 -2
- package/dist/v1/builder/callable/authoring.d.ts +10 -4
- package/dist/v1/builder/callable/authoring.js +2 -0
- package/dist/v1/builder/class/authoring.d.ts +36 -1
- package/dist/v1/builder/class/authoring.js +25 -5
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/callable.js +1 -0
- package/dist/v1/builder/lowering/class.js +18 -1
- package/dist/v1/builder/lowering/property-keys.d.ts +27 -0
- package/dist/v1/builder/lowering/property-keys.js +108 -0
- package/dist/v1/builder/lowering/schema/compile.js +8 -3
- package/dist/v1/builder/lowering/schema/context.d.ts +2 -0
- package/dist/v1/builder/lowering/schema/context.js +12 -0
- package/dist/v1/builder/lowering/schema/lifetime.d.ts +11 -0
- package/dist/v1/builder/lowering/schema/lifetime.js +55 -0
- package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
- package/dist/v1/builder/schema/projection/callable.d.ts +7 -5
- package/dist/v1/builder/schema/projection/model.d.ts +4 -2
- package/dist/v1/builder/schema/projection/resolved-class.d.ts +2 -1
- package/dist/v1/compiled/admission/semantics/cardinality.d.ts +1 -1
- package/dist/v1/compiled/admission/semantics/cardinality.js +16 -1
- package/dist/v1/compiled/admission/semantics/class.js +31 -1
- package/dist/v1/compiled/admission/semantics/core.js +16 -0
- package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -2
- package/dist/v1/compiled/admission/wire/callable.js +11 -1
- package/dist/v1/compiled/admission/wire/class.js +50 -3
- package/dist/v1/compiled/compiler/compile.js +2 -0
- package/dist/v1/domain/create.js +1 -0
- package/dist/v1/domain/projection/classes.js +3 -0
- package/dist/v1/index.d.ts +2 -1
- package/dist/v1/language/callable/admission.d.ts +15 -0
- package/dist/v1/language/callable/admission.js +43 -0
- package/dist/v1/language/callable/model/callable.d.ts +10 -0
- package/dist/v1/language/callable/resolved.d.ts +5 -1
- package/dist/v1/language/class/inheritance.d.ts +18 -5
- package/dist/v1/language/class/inheritance.js +14 -1
- package/dist/v1/language/class/lifetime.d.ts +50 -11
- package/dist/v1/language/class/lifetime.js +89 -20
- package/dist/v1/language/class/model/class.d.ts +27 -3
- package/dist/v1/language/class/model/edge.d.ts +30 -1
- package/dist/v1/language/class/unique.d.ts +143 -0
- package/dist/v1/language/class/unique.js +264 -0
- package/dist/v1/language/index.d.ts +6 -2
- package/dist/v1/language/index.js +4 -1
- package/dist/v1/language/value/evaluate/validate.js +4 -0
- package/dist/v1/language/value/evaluate/validity.optimization.d.ts +14 -0
- package/dist/v1/language/value/evaluate/validity.optimization.js +342 -0
- package/dist/v1/language/value/profile/canonicalize.js +6 -5
- package/dist/v1/schema/compatibility/api.d.ts +1 -0
- package/dist/v1/schema/compatibility/api.js +1 -0
- package/dist/v1/schema/compatibility/comparators/classes.d.ts +9 -4
- package/dist/v1/schema/compatibility/comparators/classes.js +25 -4
- package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
- package/dist/v1/schema/compatibility/extension.d.ts +11 -0
- package/dist/v1/schema/compatibility/extension.js +22 -0
- package/dist/v1/schema/compatibility/fields.d.ts +20 -4
- package/dist/v1/schema/compatibility/fields.js +13 -3
- package/dist/v1/schema/compatibility/model.d.ts +11 -3
- package/dist/v1/schema/compatibility/scope.d.ts +2 -0
- package/dist/v1/schema/compatibility/scope.js +4 -1
- package/dist/v1/schema/compatibility/structure/index.js +6 -4
- package/dist/v1/schema/compatibility/structure/requirements.js +32 -1
- package/dist/v1/schema/compatibility/versioning/surface.d.ts +1 -1
- package/dist/v1/schema/compatibility/versioning/surface.js +30 -2
- package/dist/v1/schema/diagnostic.d.ts +1 -1
- package/dist/v1/schema/normalization/domain.js +18 -0
- package/dist/v1/schema/validation/callable.d.ts +1 -1
- package/dist/v1/schema/validation/callable.js +17 -2
- package/dist/v1/schema/validation/class.js +80 -2
- package/dist/v1/schema/validation/core.js +50 -0
- package/dist/v1/schema/validation/domain.structure.js +45 -10
- package/dist/v1/schema/validation/policy/uses.js +5 -5
- package/dist/value/canonical.d.ts +5 -0
- package/dist/value/canonical.js +12 -0
- package/dist/value/json.d.ts +1 -1
- package/dist/value/json.js +1 -1
- package/package.json +1 -1
- package/dist/v1/builder/lowering/core-properties.d.ts +0 -10
- package/dist/v1/builder/lowering/core-properties.js +0 -76
|
@@ -7,10 +7,11 @@ const TYPE_RANK = new Map(TYPE_ORDER.map((name, index) => [name, index]));
|
|
|
7
7
|
const encoder = new TextEncoder();
|
|
8
8
|
/** Canonicalizes syntax without rewriting the schema's algebra. Validate first. */
|
|
9
9
|
export function canonicalizeValueSchema(schema, context, registry) {
|
|
10
|
+
void context;
|
|
10
11
|
void registry;
|
|
11
|
-
return canonicalizeSchema(schema, true
|
|
12
|
+
return canonicalizeSchema(schema, true);
|
|
12
13
|
}
|
|
13
|
-
function canonicalizeSchema(schema, root
|
|
14
|
+
function canonicalizeSchema(schema, root) {
|
|
14
15
|
if (typeof schema === 'boolean')
|
|
15
16
|
return schema;
|
|
16
17
|
const result = {};
|
|
@@ -39,17 +40,17 @@ function canonicalizeSchema(schema, root, context) {
|
|
|
39
40
|
result[keyword] = canonicalizePathAnnotation(value);
|
|
40
41
|
}
|
|
41
42
|
else if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(value)) {
|
|
42
|
-
result[keyword] = canonicalizeSchema(value, false
|
|
43
|
+
result[keyword] = canonicalizeSchema(value, false);
|
|
43
44
|
}
|
|
44
45
|
else if (SCHEMA_ARRAY_KEYWORDS.has(keyword) && Array.isArray(value)) {
|
|
45
|
-
result[keyword] = value.map((entry) => isJsonSchema(entry) ? canonicalizeSchema(entry, false
|
|
46
|
+
result[keyword] = value.map((entry) => isJsonSchema(entry) ? canonicalizeSchema(entry, false) : canonicalizeJson(entry));
|
|
46
47
|
}
|
|
47
48
|
else if (SCHEMA_MAP_KEYWORDS.has(keyword) && isJsonObject(value)) {
|
|
48
49
|
const schemas = {};
|
|
49
50
|
for (const name of Object.keys(value).sort()) {
|
|
50
51
|
const entry = value[name];
|
|
51
52
|
schemas[name] = isJsonSchema(entry)
|
|
52
|
-
? canonicalizeSchema(entry, false
|
|
53
|
+
? canonicalizeSchema(entry, false)
|
|
53
54
|
: canonicalizeJson(entry);
|
|
54
55
|
}
|
|
55
56
|
result[keyword] = schemas;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { compare, compareMeaning, compareStructure } from './compare.js';
|
|
2
|
+
export { closedParents } from './extension.js';
|
|
2
3
|
export { requiredBump } from './versioning/index.js';
|
|
3
4
|
export type { Analysis, Assessment, Bump, Comparison, ComparisonEntry, ComparisonEvidence, ComparisonRequest, ComparisonScope, Facet, Fingerprint, MeaningComparison, Observation, RequiredBump, SchemaIdentity, StructureComparison, Subject, Usage, VersionChange, } from './model.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import type { Key as SemanticKey } from '../../../addressing/index.js';
|
|
3
|
+
import type { Inheritance } from '../../../language/class/model/class.js';
|
|
3
4
|
import type { EffectiveField } from '../../analysis/class.js';
|
|
4
5
|
import type { Context } from '../context.js';
|
|
5
6
|
import type { ClassCoordinate } from '../coordinate.js';
|
|
@@ -10,10 +11,12 @@ interface ClassRequirement {
|
|
|
10
11
|
readonly ancestors: readonly SemanticKey[];
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
|
-
* A reference needs the Class to exist with its kind, abstractness and orientation
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* A reference needs the Class to exist with its kind, abstractness and orientation, and, for an
|
|
15
|
+
* Edge, the uniqueness tuples that cover it, its ancestors' included: a dependent may name one of
|
|
16
|
+
* its Edges by any of them. Inheritance additionally takes on the source ancestors, class
|
|
17
|
+
* policies, data contract and, for an Edge, the endpoint and constraint contracts. A target
|
|
18
|
+
* projected onto a source requirement keeps only the ancestors the source named, so a superset
|
|
19
|
+
* satisfies it while a removal changes it.
|
|
17
20
|
*/
|
|
18
21
|
export declare function classMeaning(context: Context, coordinate: ClassCoordinate, level: DependencyMeaningLevel, onto?: ClassRequirement): {
|
|
19
22
|
readonly meaning: JsonValue;
|
|
@@ -38,6 +41,8 @@ export declare function compareLostProperties({ pair, report, }: {
|
|
|
38
41
|
readonly pair: SubjectPair;
|
|
39
42
|
readonly report: Report;
|
|
40
43
|
}): void;
|
|
44
|
+
/** Which Classes may extend a Class, as an extension facet compares it. */
|
|
45
|
+
export declare function inheritanceMeaning(inheritance: Inheritance): JsonValue;
|
|
41
46
|
/** Newly required data is an obligation even though it was absent from the source footprint. */
|
|
42
47
|
export declare function compareAddedProperties({ context, pair, report, }: {
|
|
43
48
|
readonly context: ComparisonContext;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compareUnicode } from '../../../addressing/ordering.js';
|
|
2
|
+
import { compareEdgeUniqueTuples, coveringEdgeUniqueTuples, } from '../../../language/class/unique.js';
|
|
2
3
|
import { isClassRuleChecks } from '../../../language/policy/model/policy.js';
|
|
3
4
|
import { deepFreeze } from '../../canonical/freeze.js';
|
|
4
5
|
import { effectiveDefinition } from '../context.js';
|
|
@@ -8,10 +9,12 @@ import { CLASS_FIELDS, PROPERTY_FIELDS, fieldsOf } from '../fields.js';
|
|
|
8
9
|
import { usageOf } from '../references.js';
|
|
9
10
|
import { semanticValueSchema } from './values.js';
|
|
10
11
|
/**
|
|
11
|
-
* A reference needs the Class to exist with its kind, abstractness and orientation
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* A reference needs the Class to exist with its kind, abstractness and orientation, and, for an
|
|
13
|
+
* Edge, the uniqueness tuples that cover it, its ancestors' included: a dependent may name one of
|
|
14
|
+
* its Edges by any of them. Inheritance additionally takes on the source ancestors, class
|
|
15
|
+
* policies, data contract and, for an Edge, the endpoint and constraint contracts. A target
|
|
16
|
+
* projected onto a source requirement keeps only the ancestors the source named, so a superset
|
|
17
|
+
* satisfies it while a removal changes it.
|
|
15
18
|
*/
|
|
16
19
|
export function classMeaning(context, coordinate, level, onto) {
|
|
17
20
|
const owner = context.schemas.get(coordinate.ref.origin);
|
|
@@ -23,10 +26,12 @@ export function classMeaning(context, coordinate, level, onto) {
|
|
|
23
26
|
const requirement = Object.freeze({
|
|
24
27
|
ancestors,
|
|
25
28
|
});
|
|
29
|
+
const covering = coveringTuples(context, declaration);
|
|
26
30
|
const meaning = {
|
|
27
31
|
coordinate: 'class',
|
|
28
32
|
level: level,
|
|
29
33
|
...fieldsOf({ declaration, roles: CLASS_FIELDS, level }),
|
|
34
|
+
...(covering.length === 0 ? {} : { unique: covering }),
|
|
30
35
|
...(level === 'inherit'
|
|
31
36
|
? {
|
|
32
37
|
extends: onto === undefined
|
|
@@ -109,6 +114,18 @@ function ancestorKeys(context, ref) {
|
|
|
109
114
|
visit(ref);
|
|
110
115
|
return [...seen].sort(compareUnicode);
|
|
111
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* The uniqueness tuples covering an Edge Class (`coveringEdgeUniqueTuples`): those it and each of
|
|
119
|
+
* its ancestors declare, each with the Class declaring it, whose extent it bounds, in Class then
|
|
120
|
+
* tuple order. A Node Class's own tuples stay its declared field.
|
|
121
|
+
*/
|
|
122
|
+
function coveringTuples(context, declaration) {
|
|
123
|
+
if (declaration.kind !== 'edge')
|
|
124
|
+
return [];
|
|
125
|
+
return [
|
|
126
|
+
...coveringEdgeUniqueTuples(declaration, (ref) => context.schemas.get(ref.origin)?.classes[ref.name]),
|
|
127
|
+
].sort((left, right) => compareUnicode(left.class, right.class) || compareEdgeUniqueTuples(left.fields, right.fields));
|
|
128
|
+
}
|
|
112
129
|
/**
|
|
113
130
|
* A Class a dependent references or inherits keeps every effective Property it had, whichever
|
|
114
131
|
* ancestor declares it. Each Property is compared on its owner, which still declares it when the
|
|
@@ -139,6 +156,10 @@ function propertyIdentities(identities) {
|
|
|
139
156
|
properties: Object.fromEntries(identities.map((identity) => [identity, true])),
|
|
140
157
|
});
|
|
141
158
|
}
|
|
159
|
+
/** Which Classes may extend a Class, as an extension facet compares it. */
|
|
160
|
+
export function inheritanceMeaning(inheritance) {
|
|
161
|
+
return deepFreeze({ coordinate: 'class', inheritance });
|
|
162
|
+
}
|
|
142
163
|
/** Newly required data is an obligation even though it was absent from the source footprint. */
|
|
143
164
|
export function compareAddedProperties({ context, pair, report, }) {
|
|
144
165
|
if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
|
|
@@ -5,7 +5,7 @@ import type { ComparisonContext, SubjectPair } from '../scope.js';
|
|
|
5
5
|
export declare function compareFacet({ context, pair, facet, }: {
|
|
6
6
|
readonly context: ComparisonContext;
|
|
7
7
|
readonly pair: SubjectPair;
|
|
8
|
-
readonly facet: Exclude<Facet, 'class.composition' | 'class.properties' | 'property.required'>;
|
|
8
|
+
readonly facet: Exclude<Facet, 'class.composition' | 'class.properties' | 'class.inheritance' | 'property.required'>;
|
|
9
9
|
}): {
|
|
10
10
|
readonly before?: Value;
|
|
11
11
|
readonly after?: Value;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Key } from '../../addressing/index.js';
|
|
2
|
+
import type { DomainSchema } from '../model/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* The Classes of the target's origin that a Class of the dependent extends and the target does not
|
|
5
|
+
* open to it, as Keys in order (DM-CLASS-INHERITANCE). A parent the target no longer declares is
|
|
6
|
+
* not closed: losing it is an ancestry change.
|
|
7
|
+
*/
|
|
8
|
+
export declare function closedParents({ dependent, target, }: {
|
|
9
|
+
readonly dependent: DomainSchema;
|
|
10
|
+
readonly target: DomainSchema;
|
|
11
|
+
}): readonly Key[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { compareUnicode } from '../../addressing/ordering.js';
|
|
2
|
+
import { admitsForeignSubclass } from '../../language/class/inheritance.js';
|
|
3
|
+
import { keyForDefinitionRef } from './coordinate.js';
|
|
4
|
+
/**
|
|
5
|
+
* The Classes of the target's origin that a Class of the dependent extends and the target does not
|
|
6
|
+
* open to it, as Keys in order (DM-CLASS-INHERITANCE). A parent the target no longer declares is
|
|
7
|
+
* not closed: losing it is an ancestry change.
|
|
8
|
+
*/
|
|
9
|
+
export function closedParents({ dependent, target, }) {
|
|
10
|
+
const closed = new Set();
|
|
11
|
+
if (dependent.origin === target.origin)
|
|
12
|
+
return [];
|
|
13
|
+
for (const declaration of Object.values(dependent.classes))
|
|
14
|
+
for (const parent of declaration.extends) {
|
|
15
|
+
if (parent.origin !== target.origin)
|
|
16
|
+
continue;
|
|
17
|
+
const declared = target.classes[parent.name];
|
|
18
|
+
if (declared !== undefined && !admitsForeignSubclass(declared))
|
|
19
|
+
closed.add(keyForDefinitionRef(parent));
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze([...closed].sort(compareUnicode));
|
|
22
|
+
}
|
|
@@ -15,7 +15,7 @@ export type DependencyMeaningLevel = 'reference' | 'inherit';
|
|
|
15
15
|
* compile until it is classified, so meaning can only be narrowed by an explicit decision.
|
|
16
16
|
*/
|
|
17
17
|
export type FieldRole = 'always' | 'inherit' | 'members' | {
|
|
18
|
-
readonly excluded: 'identity' | 'annotation' | 'authorization' | 'presentation';
|
|
18
|
+
readonly excluded: 'identity' | 'annotation' | 'authorization' | 'extension' | 'presentation';
|
|
19
19
|
};
|
|
20
20
|
/** The coordinate Key already names the declaration; its `ref` is never repeated in the meaning. */
|
|
21
21
|
export declare const CLASS_FIELDS: Readonly<{
|
|
@@ -24,12 +24,16 @@ export declare const CLASS_FIELDS: Readonly<{
|
|
|
24
24
|
}>;
|
|
25
25
|
kind: "always";
|
|
26
26
|
abstract: "always";
|
|
27
|
+
inheritance: Readonly<{
|
|
28
|
+
excluded: 'extension';
|
|
29
|
+
}>;
|
|
27
30
|
orientation: "always";
|
|
28
31
|
extends: "inherit";
|
|
29
32
|
policies: "inherit";
|
|
30
33
|
data: "inherit";
|
|
31
34
|
endpoints: "inherit";
|
|
32
35
|
constraints: "inherit";
|
|
36
|
+
unique: "always";
|
|
33
37
|
properties: "members";
|
|
34
38
|
methods: "members";
|
|
35
39
|
description: Readonly<{
|
|
@@ -50,9 +54,11 @@ export declare const PROPERTY_FIELDS: Readonly<{
|
|
|
50
54
|
}>;
|
|
51
55
|
}>;
|
|
52
56
|
/**
|
|
53
|
-
* The auth level
|
|
54
|
-
*
|
|
55
|
-
*
|
|
57
|
+
* The auth level, the Policy check and the admission age are authorization: decided by the Runtime
|
|
58
|
+
* at invocation, never compiled against. The admission age only bounds how old the admission a
|
|
59
|
+
* destination decides a call under may be. The execution modifier decides what an invocation
|
|
60
|
+
* returns; an accepted Schema carries it only when durable, so ordinary callables keep their meaning
|
|
61
|
+
* unchanged.
|
|
56
62
|
*/
|
|
57
63
|
export declare const CALLABLE_FIELDS: Readonly<{
|
|
58
64
|
ref: Readonly<{
|
|
@@ -63,6 +69,9 @@ export declare const CALLABLE_FIELDS: Readonly<{
|
|
|
63
69
|
static: "always";
|
|
64
70
|
abstract: "always";
|
|
65
71
|
execution: "always";
|
|
72
|
+
admission: Readonly<{
|
|
73
|
+
excluded: 'authorization';
|
|
74
|
+
}>;
|
|
66
75
|
auth: Readonly<{
|
|
67
76
|
excluded: 'authorization';
|
|
68
77
|
}>;
|
|
@@ -114,12 +123,16 @@ export declare const FIELD_ROLES: Readonly<{
|
|
|
114
123
|
}>;
|
|
115
124
|
kind: "always";
|
|
116
125
|
abstract: "always";
|
|
126
|
+
inheritance: Readonly<{
|
|
127
|
+
excluded: 'extension';
|
|
128
|
+
}>;
|
|
117
129
|
orientation: "always";
|
|
118
130
|
extends: "inherit";
|
|
119
131
|
policies: "inherit";
|
|
120
132
|
data: "inherit";
|
|
121
133
|
endpoints: "inherit";
|
|
122
134
|
constraints: "inherit";
|
|
135
|
+
unique: "always";
|
|
123
136
|
properties: "members";
|
|
124
137
|
methods: "members";
|
|
125
138
|
description: Readonly<{
|
|
@@ -148,6 +161,9 @@ export declare const FIELD_ROLES: Readonly<{
|
|
|
148
161
|
static: "always";
|
|
149
162
|
abstract: "always";
|
|
150
163
|
execution: "always";
|
|
164
|
+
admission: Readonly<{
|
|
165
|
+
excluded: 'authorization';
|
|
166
|
+
}>;
|
|
151
167
|
auth: Readonly<{
|
|
152
168
|
excluded: 'authorization';
|
|
153
169
|
}>;
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
const IDENTITY = Object.freeze({ excluded: 'identity' });
|
|
2
2
|
const ANNOTATION = Object.freeze({ excluded: 'annotation' });
|
|
3
3
|
const AUTHORIZATION = Object.freeze({ excluded: 'authorization' });
|
|
4
|
+
/** Who may extend a Class is no meaning a dependent compiles against. */
|
|
5
|
+
const EXTENSION = Object.freeze({ excluded: 'extension' });
|
|
4
6
|
const PRESENTATION = Object.freeze({ excluded: 'presentation' });
|
|
5
7
|
/** The coordinate Key already names the declaration; its `ref` is never repeated in the meaning. */
|
|
6
8
|
export const CLASS_FIELDS = Object.freeze({
|
|
7
9
|
ref: IDENTITY,
|
|
8
10
|
kind: 'always',
|
|
9
11
|
abstract: 'always',
|
|
12
|
+
inheritance: EXTENSION,
|
|
10
13
|
orientation: 'always',
|
|
11
14
|
extends: 'inherit',
|
|
12
15
|
policies: 'inherit',
|
|
13
16
|
data: 'inherit',
|
|
14
17
|
endpoints: 'inherit',
|
|
15
18
|
constraints: 'inherit',
|
|
19
|
+
// A uniqueness tuple bounds every write of the Classes it covers and what a step through them
|
|
20
|
+
// reaches, so every dependent that names the Class relies on it, not only one that extends it.
|
|
21
|
+
// The meaning holds every tuple covering the Class, an ancestor's included (`classMeaning`).
|
|
22
|
+
unique: 'always',
|
|
16
23
|
properties: 'members',
|
|
17
24
|
methods: 'members',
|
|
18
25
|
description: ANNOTATION,
|
|
@@ -27,9 +34,11 @@ export const PROPERTY_FIELDS = Object.freeze({
|
|
|
27
34
|
description: ANNOTATION,
|
|
28
35
|
});
|
|
29
36
|
/**
|
|
30
|
-
* The auth level
|
|
31
|
-
*
|
|
32
|
-
*
|
|
37
|
+
* The auth level, the Policy check and the admission age are authorization: decided by the Runtime
|
|
38
|
+
* at invocation, never compiled against. The admission age only bounds how old the admission a
|
|
39
|
+
* destination decides a call under may be. The execution modifier decides what an invocation
|
|
40
|
+
* returns; an accepted Schema carries it only when durable, so ordinary callables keep their meaning
|
|
41
|
+
* unchanged.
|
|
33
42
|
*/
|
|
34
43
|
export const CALLABLE_FIELDS = Object.freeze({
|
|
35
44
|
ref: IDENTITY,
|
|
@@ -38,6 +47,7 @@ export const CALLABLE_FIELDS = Object.freeze({
|
|
|
38
47
|
static: 'always',
|
|
39
48
|
abstract: 'always',
|
|
40
49
|
execution: 'always',
|
|
50
|
+
admission: AUTHORIZATION,
|
|
41
51
|
auth: AUTHORIZATION,
|
|
42
52
|
policy: AUTHORIZATION,
|
|
43
53
|
description: ANNOTATION,
|
|
@@ -17,6 +17,13 @@ export interface ComparisonRequest {
|
|
|
17
17
|
* unresolvable or unsupported Key of the target origin, is rejected with DM_CAPABILITY_INVALID.
|
|
18
18
|
*/
|
|
19
19
|
readonly capabilities?: readonly Key[];
|
|
20
|
+
/**
|
|
21
|
+
* `true` when the dependent already holds its binding to the target, as the installer holds
|
|
22
|
+
* one a Domain was installed with: the Classes of the target origin it extends are then not
|
|
23
|
+
* judged for inheritance (DM-CLASS-INHERITANCE). Otherwise each must be `'open'` in the
|
|
24
|
+
* target. It enters no evidence: a held comparison has the facts of a compatible judged one.
|
|
25
|
+
*/
|
|
26
|
+
readonly held?: boolean;
|
|
20
27
|
} | {
|
|
21
28
|
readonly kind: 'members';
|
|
22
29
|
readonly source: DomainSchema;
|
|
@@ -46,7 +53,7 @@ export interface Usage {
|
|
|
46
53
|
readonly key?: Key;
|
|
47
54
|
readonly relation: ReferenceSite | 'member' | 'abstract' | 'selection' | 'capability';
|
|
48
55
|
}
|
|
49
|
-
export type Facet = 'class.reference' | 'class.inherit' | 'class.composition' | 'class.properties' | 'class.endpoints' | 'property' | 'property.required' | 'method.existence' | 'method.object' | 'method.inheritance' | 'method.signature' | 'function.existence' | 'function.signature' | 'policy' | 'view' | 'core';
|
|
56
|
+
export type Facet = 'class.reference' | 'class.inherit' | 'class.composition' | 'class.properties' | 'class.endpoints' | 'class.inheritance' | 'property' | 'property.required' | 'method.existence' | 'method.object' | 'method.inheritance' | 'method.signature' | 'function.existence' | 'function.signature' | 'policy' | 'view' | 'core';
|
|
50
57
|
/** A fact about a compared facet, including unchanged facets. No admission decision is implied. */
|
|
51
58
|
export interface Observation {
|
|
52
59
|
readonly id: string;
|
|
@@ -81,8 +88,9 @@ export interface Assessment {
|
|
|
81
88
|
* meaning evidence never records whether capabilities were declared. A referenced or inherited Class
|
|
82
89
|
* that loses an effective Property (`class.properties`) raises structure to version 4 and meaning to
|
|
83
90
|
* version 2, and structure is version 4 too when it records that a dependent Policy lost what it
|
|
84
|
-
* relies on (`method.object`, `class.endpoints`)
|
|
85
|
-
*
|
|
91
|
+
* relies on (`method.object`, `class.endpoints`) or that a Class the dependent extends became final
|
|
92
|
+
* (`class.inheritance`). Those facts are recorded only on such a loss, so every other comparison,
|
|
93
|
+
* every compatible one included, keeps its bytes.
|
|
86
94
|
*/
|
|
87
95
|
export interface ComparisonEvidence {
|
|
88
96
|
readonly kind: 'structure' | 'meaning';
|
|
@@ -25,6 +25,8 @@ export interface ComparisonContext {
|
|
|
25
25
|
* structure records them only when they fail, so a compatible comparison keeps its bytes.
|
|
26
26
|
*/
|
|
27
27
|
readonly reliance: readonly Reference[];
|
|
28
|
+
/** Whether the dependent holds its binding to the target, so its extensions are not judged. */
|
|
29
|
+
readonly held: boolean;
|
|
28
30
|
pair(coordinate: Coordinate): SubjectPair;
|
|
29
31
|
}
|
|
30
32
|
export declare function prepare(request: ComparisonRequest): ComparisonContext;
|
|
@@ -42,6 +42,7 @@ export function prepare(request) {
|
|
|
42
42
|
roots: [...rootReferences(dependent), ...capabilities],
|
|
43
43
|
capabilities,
|
|
44
44
|
reliance: policyReliance(dependent, source.origin),
|
|
45
|
+
held: scope.held === true,
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
@@ -63,9 +64,10 @@ export function selectMembers(source, target, keys) {
|
|
|
63
64
|
})),
|
|
64
65
|
capabilities: [],
|
|
65
66
|
reliance: [],
|
|
67
|
+
held: false,
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
|
-
function comparisonContext({ kind, source, target, dependent, before, roots, capabilities, reliance, }) {
|
|
70
|
+
function comparisonContext({ kind, source, target, dependent, before, roots, capabilities, reliance, held, }) {
|
|
69
71
|
resolve(target);
|
|
70
72
|
const after = contextOf(target);
|
|
71
73
|
const pairs = new Map();
|
|
@@ -78,6 +80,7 @@ function comparisonContext({ kind, source, target, dependent, before, roots, cap
|
|
|
78
80
|
roots,
|
|
79
81
|
capabilities,
|
|
80
82
|
reliance,
|
|
83
|
+
held,
|
|
81
84
|
scope: Object.freeze({
|
|
82
85
|
kind,
|
|
83
86
|
source: schemaIdentity(source),
|
|
@@ -12,8 +12,9 @@ export function analyzeStructure(context) {
|
|
|
12
12
|
...result,
|
|
13
13
|
compatible: result.assessments.every(({ satisfied }) => satisfied),
|
|
14
14
|
// Each version adds one requirement policy and applies only when its fact is present: 3 a
|
|
15
|
-
// declared capability root, 4 a lost effective Property of a reached Class
|
|
16
|
-
// reliance, facts recorded only when they fail. Without one, evidence
|
|
15
|
+
// declared capability root, 4 a lost effective Property of a reached Class, a failed Policy
|
|
16
|
+
// reliance or a refused extension, facts recorded only when they fail. Without one, evidence
|
|
17
|
+
// keeps its version-2 bytes.
|
|
17
18
|
evidence: evidenceOf({
|
|
18
19
|
kind: 'structure',
|
|
19
20
|
version: recordsFailureOnlyFact(result.assessments)
|
|
@@ -27,13 +28,14 @@ export function analyzeStructure(context) {
|
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* The facts structure records only when they fail: a referenced or inherited Class that lost an
|
|
30
|
-
* effective Property,
|
|
31
|
-
* endpoints).
|
|
31
|
+
* effective Property, what a dependent Policy relies on (a Method object, a matched Edge's
|
|
32
|
+
* endpoints), and a Class the dependent extends that became final.
|
|
32
33
|
*/
|
|
33
34
|
const FAILURE_ONLY_FACETS = new Set([
|
|
34
35
|
'class.properties',
|
|
35
36
|
'method.object',
|
|
36
37
|
'class.endpoints',
|
|
38
|
+
'class.inheritance',
|
|
37
39
|
]);
|
|
38
40
|
function recordsFailureOnlyFact(assessments) {
|
|
39
41
|
return assessments.some(({ requirement }) => FAILURE_ONLY_FACETS.has(requirement.kind));
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Key } from '../../../addressing/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { inheritanceOf } from '../../../language/class/inheritance.js';
|
|
3
|
+
import { compareAddedProperties, compareLostProperties, inheritanceMeaning, } from '../comparators/classes.js';
|
|
3
4
|
import { compareFacet } from '../comparators/index.js';
|
|
4
5
|
import { fingerprintMeaning } from '../comparators/values.js';
|
|
5
6
|
import { contextInvalid } from '../context.js';
|
|
6
7
|
import { methodCoordinate } from '../coordinate.js';
|
|
8
|
+
import { closedParents } from '../extension.js';
|
|
7
9
|
import { callableReferences, classReferences, inherits, memberReferences, usageOf, valueReferences, } from '../references.js';
|
|
8
10
|
/** Structure follows data and abstract obligations; concrete IO never expands its requirements. */
|
|
9
11
|
export function compareRequirements({ context, report, }) {
|
|
@@ -73,6 +75,7 @@ export function compareRequirements({ context, report, }) {
|
|
|
73
75
|
compareLostProperties({ pair, report });
|
|
74
76
|
}
|
|
75
77
|
compareReliance({ context, report });
|
|
78
|
+
compareExtension({ context, report });
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
78
81
|
* What a dependent Policy relies on is recorded only when it fails: a Method it names that is gone
|
|
@@ -95,3 +98,31 @@ function compareReliance({ context, report, }) {
|
|
|
95
98
|
report.record({ pair, facet, before, after, requirement: { kind: facet } });
|
|
96
99
|
}
|
|
97
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* A Class of the compared origin one of the dependent's own Classes extends must be `'open'` in the
|
|
103
|
+
* target, unless the dependent holds its binding to that target. The fact is recorded only when it
|
|
104
|
+
* fails, like a Policy reliance, so a compatible comparison keeps its evidence bytes: `'open'`, what
|
|
105
|
+
* a subclass of another Domain requires, before, and the target's inheritance after.
|
|
106
|
+
*/
|
|
107
|
+
function compareExtension({ context, report, }) {
|
|
108
|
+
if (context.held || context.dependent === undefined)
|
|
109
|
+
return;
|
|
110
|
+
const closed = new Set(closedParents({ dependent: context.dependent, target: context.target }));
|
|
111
|
+
if (closed.size === 0)
|
|
112
|
+
return;
|
|
113
|
+
for (const reference of context.roots) {
|
|
114
|
+
if (reference.usage.relation !== 'extends' || !closed.has(reference.coordinate.key))
|
|
115
|
+
continue;
|
|
116
|
+
const pair = context.pair(reference.coordinate);
|
|
117
|
+
if (pair.target?.kind !== 'class')
|
|
118
|
+
continue;
|
|
119
|
+
report.note(reference);
|
|
120
|
+
report.record({
|
|
121
|
+
pair,
|
|
122
|
+
facet: 'class.inheritance',
|
|
123
|
+
before: inheritanceMeaning('open'),
|
|
124
|
+
after: inheritanceMeaning(inheritanceOf(pair.target.analysis.declaration)),
|
|
125
|
+
requirement: { kind: 'class.inheritance' },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Report } from '../report.js';
|
|
2
2
|
import type { ComparisonContext } from '../scope.js';
|
|
3
3
|
/**
|
|
4
|
-
* Compare inherited contracts
|
|
4
|
+
* Compare inherited contracts, newly imposed obligations and who may extend existing Classes.
|
|
5
5
|
* Obligations are reported at their declaring Method: changed signatures retain their comparison;
|
|
6
6
|
* an unchanged obligation newly inherited by a Class is added and unsatisfied.
|
|
7
7
|
*/
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { admitsForeignSubclass, inheritanceOf } from '../../../language/class/inheritance.js';
|
|
1
2
|
import { methodInheritance } from '../comparators/callables.js';
|
|
2
|
-
import { compareAddedProperties } from '../comparators/classes.js';
|
|
3
|
+
import { compareAddedProperties, inheritanceMeaning } from '../comparators/classes.js';
|
|
3
4
|
import { compareFacet } from '../comparators/index.js';
|
|
4
5
|
import { fingerprintMeaning } from '../comparators/values.js';
|
|
5
6
|
import { classCoordinate, methodCoordinate } from '../coordinate.js';
|
|
6
7
|
/**
|
|
7
|
-
* Compare inherited contracts
|
|
8
|
+
* Compare inherited contracts, newly imposed obligations and who may extend existing Classes.
|
|
8
9
|
* Obligations are reported at their declaring Method: changed signatures retain their comparison;
|
|
9
10
|
* an unchanged obligation newly inherited by a Class is added and unsatisfied.
|
|
10
11
|
*/
|
|
@@ -25,6 +26,7 @@ export function compareSurface({ context, report, }) {
|
|
|
25
26
|
requirement: { kind: 'class.inherit' },
|
|
26
27
|
});
|
|
27
28
|
compareAddedProperties({ context, pair, report });
|
|
29
|
+
compareExtension({ pair, report });
|
|
28
30
|
for (const [key, method] of source.analysis.requirements)
|
|
29
31
|
if (!established.has(key))
|
|
30
32
|
established.set(key, method);
|
|
@@ -54,6 +56,32 @@ export function compareSurface({ context, report, }) {
|
|
|
54
56
|
});
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* A changed inheritance is observed on its Class, and is major when an `'open'` Class stops being
|
|
61
|
+
* open: only an `'open'` Class admits a subclass of another Domain (DM-CLASS-INHERITANCE). Closing
|
|
62
|
+
* or finalizing a Class no other Domain may extend takes nothing from a dependent; opening one
|
|
63
|
+
* grants an extension, which the floor, counting added members only, leaves at a patch.
|
|
64
|
+
*/
|
|
65
|
+
function compareExtension({ pair, report, }) {
|
|
66
|
+
if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
|
|
67
|
+
return;
|
|
68
|
+
const source = pair.source.analysis.declaration;
|
|
69
|
+
const target = pair.target.analysis.declaration;
|
|
70
|
+
const before = inheritanceOf(source);
|
|
71
|
+
const after = inheritanceOf(target);
|
|
72
|
+
if (before === after)
|
|
73
|
+
return;
|
|
74
|
+
report.record({
|
|
75
|
+
pair,
|
|
76
|
+
facet: 'class.inheritance',
|
|
77
|
+
before: inheritanceMeaning(before),
|
|
78
|
+
after: inheritanceMeaning(after),
|
|
79
|
+
requirement: {
|
|
80
|
+
kind: 'class.inheritance',
|
|
81
|
+
satisfied: !admitsForeignSubclass(source) || admitsForeignSubclass(target),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
57
85
|
function changed({ before, after }) {
|
|
58
86
|
return (before === undefined ||
|
|
59
87
|
after === undefined ||
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_FOREIGN_MINIMUM' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
|
|
1
|
+
export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_ADMISSION' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_INHERITANCE' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_EDGE_UNIQUE' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_NODE_UNIQUE' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_FOREIGN_MINIMUM' | 'DV_EDGE_LIFETIME' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_EDGE_UNIQUE' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_NODE_UNIQUE' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
|
|
2
2
|
export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
|
|
3
3
|
readonly code: Code;
|
|
4
4
|
readonly pointer: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { compareDefinitionRefs, compareUnicode } from '../../addressing/ordering.js';
|
|
3
3
|
import { isBinaryInput } from '../../language/callable/model/callable.js';
|
|
4
|
+
import { compareEdgeUniqueFields, compareEdgeUniqueTuples, compareNodeUniqueTuples, } from '../../language/class/unique.js';
|
|
4
5
|
import { normalizeClassRule, normalizePolicyChecks, normalizePolicyExpression, } from '../../language/policy/normalization/normalize.js';
|
|
5
6
|
import { admitStateTopology } from '../../language/state/topology.js';
|
|
6
7
|
import { canonicalizeValueSchema } from '../../language/value/profile/canonicalize.js';
|
|
@@ -61,6 +62,7 @@ function canonicalizeClass(value) {
|
|
|
61
62
|
ref: value.ref,
|
|
62
63
|
kind: value.kind,
|
|
63
64
|
abstract: value.abstract,
|
|
65
|
+
...(value.inheritance === undefined ? {} : { inheritance: value.inheritance }),
|
|
64
66
|
extends: [...value.extends].sort(compareDefinitionRefs),
|
|
65
67
|
properties: mapRecord(value.properties, canonicalizeProperty),
|
|
66
68
|
};
|
|
@@ -71,6 +73,7 @@ function canonicalizeClass(value) {
|
|
|
71
73
|
...(value.data === undefined ? {} : { data: { mediaType: value.data.mediaType } }),
|
|
72
74
|
...(value.icon === undefined ? {} : { icon: value.icon }),
|
|
73
75
|
policies: canonicalizeNodePolicies(value.policies),
|
|
76
|
+
...(value.unique === undefined ? {} : { unique: canonicalizeNodeUnique(value.unique) }),
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
return value.orientation === 'directed'
|
|
@@ -80,6 +83,7 @@ function canonicalizeClass(value) {
|
|
|
80
83
|
endpoints: canonicalizeDirectedEndpoints(value.endpoints),
|
|
81
84
|
policies: value.policies,
|
|
82
85
|
constraints: value.constraints,
|
|
86
|
+
...(value.unique === undefined ? {} : { unique: canonicalizeUnique(value.unique) }),
|
|
83
87
|
}
|
|
84
88
|
: {
|
|
85
89
|
...base,
|
|
@@ -89,6 +93,18 @@ function canonicalizeClass(value) {
|
|
|
89
93
|
constraints: value.constraints,
|
|
90
94
|
};
|
|
91
95
|
}
|
|
96
|
+
/** A set of Node key tuples, each a set of Property keys, both in their canonical order. */
|
|
97
|
+
function canonicalizeNodeUnique(tuples) {
|
|
98
|
+
return tuples
|
|
99
|
+
.map((tuple) => [...tuple].sort(compareUnicode))
|
|
100
|
+
.sort(compareNodeUniqueTuples);
|
|
101
|
+
}
|
|
102
|
+
/** A set of key tuples, each a set of fields: fields and tuples take their canonical order. */
|
|
103
|
+
function canonicalizeUnique(tuples) {
|
|
104
|
+
return tuples
|
|
105
|
+
.map((tuple) => [...tuple].sort(compareEdgeUniqueFields))
|
|
106
|
+
.sort(compareEdgeUniqueTuples);
|
|
107
|
+
}
|
|
92
108
|
/** A rule keeps one spelling: normalized checks, or the Policy a lone `self` check names. */
|
|
93
109
|
function canonicalizeNodePolicies(value) {
|
|
94
110
|
return {
|
|
@@ -115,6 +131,8 @@ function canonicalizeCallable(value) {
|
|
|
115
131
|
output: canonicalizeCallableOutput(value.output),
|
|
116
132
|
// Ordinary execution has one canonical form, so existing revisions never move.
|
|
117
133
|
...(value.execution?.durable === true ? { execution: { durable: true } } : {}),
|
|
134
|
+
// Absence is the one spelling of a call admitted afresh; validation refuses any other.
|
|
135
|
+
...(value.admission === undefined ? {} : { admission: { maxAge: value.admission.maxAge } }),
|
|
118
136
|
};
|
|
119
137
|
return value.auth === 'authorized'
|
|
120
138
|
? {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { SchemaDiagnostic } from '../diagnostic.js';
|
|
2
2
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
3
|
-
/** Validate the execution modifier of every local Function and Node Method. */
|
|
3
|
+
/** Validate the execution modifier and admission age of every local Function and Node Method. */
|
|
4
4
|
export declare function validateCallables(schema: DomainSchemaIR): readonly SchemaDiagnostic[];
|