@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
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from '../../language/callable/admission.js';
|
|
1
2
|
import { isBinaryInput } from '../../language/callable/model/callable.js';
|
|
2
3
|
import { diagnostic } from '../diagnostic.js';
|
|
3
|
-
/** Validate the execution modifier of every local Function and Node Method. */
|
|
4
|
+
/** Validate the execution modifier and admission age of every local Function and Node Method. */
|
|
4
5
|
export function validateCallables(schema) {
|
|
5
6
|
const diagnostics = [];
|
|
6
7
|
for (const [name, declaration] of Object.entries(schema.functions)) {
|
|
7
|
-
|
|
8
|
+
const pointer = `/functions/${escapePointer(name)}`;
|
|
9
|
+
validateExecution(declaration, pointer, diagnostics);
|
|
10
|
+
validateAdmission(declaration, pointer, diagnostics);
|
|
8
11
|
}
|
|
9
12
|
for (const [name, declaration] of Object.entries(schema.classes)) {
|
|
10
13
|
if (declaration.kind !== 'node')
|
|
@@ -12,10 +15,22 @@ export function validateCallables(schema) {
|
|
|
12
15
|
for (const [method, value] of Object.entries(declaration.methods)) {
|
|
13
16
|
const pointer = `/classes/${escapePointer(name)}/methods/${escapePointer(method)}`;
|
|
14
17
|
validateExecution(value, pointer, diagnostics);
|
|
18
|
+
validateAdmission(value, pointer, diagnostics);
|
|
15
19
|
}
|
|
16
20
|
}
|
|
17
21
|
return Object.freeze(diagnostics);
|
|
18
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* An admission age is a whole number of seconds within the language ceiling. It has no zero
|
|
25
|
+
* spelling: an absent admission age is the one canonical form of a call admitted afresh.
|
|
26
|
+
*/
|
|
27
|
+
function validateAdmission(declaration, pointer, diagnostics) {
|
|
28
|
+
if (declaration.admission === undefined)
|
|
29
|
+
return;
|
|
30
|
+
if (isAdmissionMaxAge(declaration.admission.maxAge))
|
|
31
|
+
return;
|
|
32
|
+
diagnostics.push(diagnostic('DV_CALLABLE_ADMISSION', `${pointer}/admission/maxAge`, `An admission age is an integer number of seconds from 1 to ${MAXIMUM_ADMISSION_AGE_SECONDS}; omit it to admit every call afresh.`));
|
|
33
|
+
}
|
|
19
34
|
/**
|
|
20
35
|
* An Execution records its initiating caller and its admitted input, from which the run is
|
|
21
36
|
* resubmitted, and retains one portable outcome. A durable callable therefore needs a known caller,
|
|
@@ -2,16 +2,19 @@ import { Key } from '../../addressing/key.js';
|
|
|
2
2
|
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
3
|
import { isMethodExecutable } from '../../language/callable/executable.js';
|
|
4
4
|
import { classSatisfies as satisfiesClass, isGraphRoot } from '../../language/class/inheritance.js';
|
|
5
|
+
import { lifetimeBreaches, lifetimeScope } from '../../language/class/lifetime.js';
|
|
6
|
+
import { keyableProperty } from '../../language/class/unique.js';
|
|
5
7
|
import { admitStateTopology } from '../../language/state/topology.js';
|
|
6
8
|
import { isBlobValueSchema } from '../../language/value/model/blob.js';
|
|
7
9
|
import { classifyOrdering } from '../../language/value/ordering.js';
|
|
8
10
|
import { diagnostic } from '../diagnostic.js';
|
|
9
|
-
import { createValueSchemaRegistry } from '../resolution/value.registry.js';
|
|
11
|
+
import { createDomainValueSchemaRegistry, createValueSchemaRegistry, } from '../resolution/value.registry.js';
|
|
10
12
|
const formatDefinitionRef = Key.of;
|
|
11
13
|
/** Validate the semantic contracts owned by Class declarations. */
|
|
12
14
|
export function validateDefinitions(environment, analysis) {
|
|
13
15
|
const diagnostics = [...analysis.diagnostics];
|
|
14
16
|
const schema = environment.candidate;
|
|
17
|
+
let scope;
|
|
15
18
|
for (const [name, declaration] of Object.entries(schema.classes)) {
|
|
16
19
|
const ref = localRef(schema.origin, name);
|
|
17
20
|
const pointer = `/classes/${escapePointer(name)}`;
|
|
@@ -26,7 +29,10 @@ export function validateDefinitions(environment, analysis) {
|
|
|
26
29
|
validateParents(environment, ref, declaration.kind, declaration.extends, diagnostics);
|
|
27
30
|
if (declaration.kind === 'edge') {
|
|
28
31
|
validateEdgeContract(environment, ref, declaration, diagnostics);
|
|
32
|
+
scope ??= lifetimeScope([...environment.definitions.values()].map(({ declaration }) => declaration));
|
|
33
|
+
validateEdgeLifetimes(scope, ref, declaration, pointer, diagnostics);
|
|
29
34
|
}
|
|
35
|
+
validateUniqueProperties(environment, analysis, ref, declaration, pointer, diagnostics);
|
|
30
36
|
}
|
|
31
37
|
validateEdgeClassComposition(environment, diagnostics);
|
|
32
38
|
return Object.freeze(diagnostics);
|
|
@@ -87,9 +93,15 @@ function validateFacet(environment, declaration, pointer, diagnostics) {
|
|
|
87
93
|
return;
|
|
88
94
|
for (const [name, method] of Object.entries(declaration.methods)) {
|
|
89
95
|
const methodPointer = `${pointer}/methods/${escapePointer(name)}`;
|
|
90
|
-
if (
|
|
96
|
+
if (isMethodExecutable(declaration, method))
|
|
97
|
+
continue;
|
|
98
|
+
if (method.policy !== undefined) {
|
|
91
99
|
diagnostics.push(diagnostic('DV_METHOD_ABSTRACT', `${methodPointer}/policy`, 'Abstract Methods cannot have a Policy.'));
|
|
92
100
|
}
|
|
101
|
+
// An admission age qualifies the admission of an executable declaration, like its Policy.
|
|
102
|
+
if (method.admission !== undefined) {
|
|
103
|
+
diagnostics.push(diagnostic('DV_METHOD_ABSTRACT', `${methodPointer}/admission`, 'Abstract Methods cannot have an admission age.'));
|
|
104
|
+
}
|
|
93
105
|
}
|
|
94
106
|
}
|
|
95
107
|
function validateParents(environment, owner, ownerKind, parents, diagnostics) {
|
|
@@ -103,6 +115,9 @@ function validateParents(environment, owner, ownerKind, parents, diagnostics) {
|
|
|
103
115
|
if (target.ref.kind !== 'class' || target.declaration.kind !== ownerKind) {
|
|
104
116
|
diagnostics.push(diagnostic('DV_CLASS_KIND', pointer, `${formatDefinitionRef(owner)} cannot extend different-kind Class ${formatDefinitionRef(parent)}.`, formatDefinitionRef(parent)));
|
|
105
117
|
}
|
|
118
|
+
else if (target.declaration.inheritance === 'final') {
|
|
119
|
+
diagnostics.push(diagnostic('DV_CLASS_INHERITANCE', pointer, `${formatDefinitionRef(owner)} cannot extend final Class ${formatDefinitionRef(parent)}.`, formatDefinitionRef(parent)));
|
|
120
|
+
}
|
|
106
121
|
}
|
|
107
122
|
}
|
|
108
123
|
function validateEdgeContract(environment, owner, declaration, diagnostics) {
|
|
@@ -123,6 +138,56 @@ function validateEdgeContract(environment, owner, declaration, diagnostics) {
|
|
|
123
138
|
}
|
|
124
139
|
}
|
|
125
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Each Property a key tuple names is one of the Class's own or inherited Properties, and keyable:
|
|
143
|
+
* a single-kind scalar by its ordering facet that no automatic program writes (D-S1). Properties
|
|
144
|
+
* inherit without redefinition, so the key reads one meaning on every descendant it covers. A Node
|
|
145
|
+
* tuple names Properties only; an Edge tuple names them as `{ property }` beside its other fields.
|
|
146
|
+
*/
|
|
147
|
+
function validateUniqueProperties(environment, analysis, owner, declaration, pointer, diagnostics) {
|
|
148
|
+
if (declaration.kind === 'edge' && declaration.orientation !== 'directed')
|
|
149
|
+
return;
|
|
150
|
+
if (declaration.unique === undefined)
|
|
151
|
+
return;
|
|
152
|
+
const properties = analysis.definitions.get(definitionRefIndex(owner))?.properties;
|
|
153
|
+
if (properties === undefined)
|
|
154
|
+
return;
|
|
155
|
+
const code = declaration.kind === 'node' ? 'DV_NODE_UNIQUE' : 'DV_EDGE_UNIQUE';
|
|
156
|
+
const registries = new Map();
|
|
157
|
+
const registryOf = (origin) => {
|
|
158
|
+
if (!registries.has(origin)) {
|
|
159
|
+
const schema = environment.dependencies.get(origin);
|
|
160
|
+
const registry = origin === environment.candidate.origin
|
|
161
|
+
? createValueSchemaRegistry(environment)
|
|
162
|
+
: schema === undefined
|
|
163
|
+
? undefined
|
|
164
|
+
: createDomainValueSchemaRegistry(schema);
|
|
165
|
+
if (registry !== undefined)
|
|
166
|
+
registries.set(origin, registry);
|
|
167
|
+
}
|
|
168
|
+
return registries.get(origin);
|
|
169
|
+
};
|
|
170
|
+
const check = (key, at) => {
|
|
171
|
+
const property = properties.get(key);
|
|
172
|
+
if (property === undefined) {
|
|
173
|
+
diagnostics.push(diagnostic(code, at, `${key} is not a Property of the Class.`));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const registry = registryOf(property.owner.origin);
|
|
177
|
+
const ordering = registry === undefined ? undefined : classifyOrdering(property.schema, registry);
|
|
178
|
+
if (!keyableProperty(ordering, property.auto)) {
|
|
179
|
+
diagnostics.push(diagnostic(code, at, 'A key Property is a string, number, boolean or instant that no automatic program writes.'));
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
if (declaration.kind === 'node') {
|
|
183
|
+
declaration.unique.forEach((tuple, t) => tuple.forEach((key, f) => check(key, `${pointer}/unique/${t}/${f}`)));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
declaration.unique.forEach((tuple, t) => tuple.forEach((field, f) => {
|
|
187
|
+
if (typeof field !== 'string')
|
|
188
|
+
check(field.property, `${pointer}/unique/${t}/${f}/property`);
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
126
191
|
function validateEndpoint(environment, owner, endpoint, count, side, diagnostics) {
|
|
127
192
|
// An Edge Class binds only the Nodes of its Domain and its dependents: it cannot oblige the
|
|
128
193
|
// Nodes of a Class another Domain owns to carry its Edges.
|
|
@@ -145,6 +210,19 @@ function validateEndpoint(environment, owner, endpoint, count, side, diagnostics
|
|
|
145
210
|
}
|
|
146
211
|
}
|
|
147
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* The lifetime laws over the lifetimes a Schema declares. What an endpoint derives where it
|
|
215
|
+
* declares nothing is never refused here: a stored Registry decoded again keeps it.
|
|
216
|
+
*/
|
|
217
|
+
function validateEdgeLifetimes(scope, owner, declaration, pointer, diagnostics) {
|
|
218
|
+
const sides = declaration.orientation === 'directed'
|
|
219
|
+
? ['source', 'target']
|
|
220
|
+
: ['first endpoint', 'second endpoint'];
|
|
221
|
+
const keys = declaration.orientation === 'directed' ? ['source', 'target'] : ['0', '1'];
|
|
222
|
+
for (const { index, reason } of lifetimeBreaches(declaration, scope)) {
|
|
223
|
+
diagnostics.push(diagnostic('DV_EDGE_LIFETIME', `${pointer}/endpoints/${keys[index]}/onDelete`, `${formatDefinitionRef(owner)} ${sides[index]} ${reason}`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
148
226
|
/** Edge inheritance is refinement, not a second interface composition mechanism. */
|
|
149
227
|
function validateEdgeClassComposition(environment, diagnostics) {
|
|
150
228
|
for (const resolved of environment.definitions.values()) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Key } from '../../addressing/key.js';
|
|
2
2
|
import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
3
|
import { isMethodExecutable } from '../../language/callable/executable.js';
|
|
4
|
+
import { coveringEdgeUniqueTuples, coveringNodeUniqueTuples, edgeUniqueKeys, nodeUniqueKeys, } from '../../language/class/unique.js';
|
|
4
5
|
import { policyObjectKey, policyObjectOrigin, projectedMemberClass, } from '../../language/policy/model/policy.js';
|
|
5
6
|
import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
|
|
6
7
|
import { diagnostic } from '../diagnostic.js';
|
|
@@ -10,9 +11,34 @@ export function validateCore(environment, analysis, registry) {
|
|
|
10
11
|
const diagnostics = [];
|
|
11
12
|
const nodesByRef = resolveCoreNodes(environment, analysis, registry, diagnostics);
|
|
12
13
|
const edges = resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnostics);
|
|
14
|
+
validateCoreNodeKeys(environment, nodesByRef, diagnostics);
|
|
13
15
|
validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics);
|
|
14
16
|
return Object.freeze(diagnostics);
|
|
15
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* No two Core Nodes hold one key under a tuple covering their Classes, whichever Class or ancestor
|
|
20
|
+
* declares it (DV-NODE-UNIQUE); Activation checks them against stored data.
|
|
21
|
+
*/
|
|
22
|
+
function validateCoreNodeKeys(environment, nodesByRef, diagnostics) {
|
|
23
|
+
const resolve = (ref) => environment.resolveAny(ref)?.declaration;
|
|
24
|
+
const holders = new Map();
|
|
25
|
+
for (const node of nodesByRef.values()) {
|
|
26
|
+
const declaration = resolve(node.classRef);
|
|
27
|
+
if (declaration === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
const pointer = `/core/nodes/${escapePointer(node.slug)}`;
|
|
30
|
+
const keys = nodeUniqueKeys(coveringNodeUniqueTuples(declaration, resolve), {
|
|
31
|
+
props: environment.candidate.core.nodes[node.slug].properties,
|
|
32
|
+
});
|
|
33
|
+
const previous = keys.map((key) => holders.get(key)).find((at) => at !== undefined);
|
|
34
|
+
if (previous !== undefined) {
|
|
35
|
+
diagnostics.push(diagnostic('DV_CORE_NODE_UNIQUE', pointer, `Core Node holds a key the Core Node at ${previous} holds.`));
|
|
36
|
+
}
|
|
37
|
+
for (const key of keys)
|
|
38
|
+
if (!holders.has(key))
|
|
39
|
+
holders.set(key, pointer);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
16
42
|
function resolveCoreNodes(environment, analysis, registry, diagnostics) {
|
|
17
43
|
const nodes = new Map();
|
|
18
44
|
for (const [slug, node] of Object.entries(environment.candidate.core.nodes)) {
|
|
@@ -175,6 +201,7 @@ function validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics) {
|
|
|
175
201
|
diagnostics.push(diagnostic('DV_CORE_EDGE_CONSTRAINTS', edge.pointer, 'Core Edge violates noSelf.'));
|
|
176
202
|
}
|
|
177
203
|
}
|
|
204
|
+
validateCoreEdgeKeys(environment, edges, diagnostics);
|
|
178
205
|
for (const { declaration, edges: classEdges } of edgeSlices.values()) {
|
|
179
206
|
if (declaration.constraints.acyclic === true && hasDirectedCycle(classEdges)) {
|
|
180
207
|
diagnostics.push(diagnostic('DV_CORE_EDGE_CONSTRAINTS', '/core/edges', 'Core Edge set violates exact-Class acyclicity.'));
|
|
@@ -182,6 +209,29 @@ function validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics) {
|
|
|
182
209
|
validateCardinality(environment, declaration, classEdges, nodesByRef, diagnostics);
|
|
183
210
|
}
|
|
184
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* No two Core Edges hold one key under a tuple covering their Classes, whichever Class or ancestor
|
|
214
|
+
* declares it (DV-EDGE-UNIQUE). Core Edges have no slug, so only tuples of endpoints and
|
|
215
|
+
* Properties can bind them; Activation checks them against stored data.
|
|
216
|
+
*/
|
|
217
|
+
function validateCoreEdgeKeys(environment, edges, diagnostics) {
|
|
218
|
+
const resolve = (ref) => environment.resolveAny(ref)?.declaration;
|
|
219
|
+
const holders = new Map();
|
|
220
|
+
for (const edge of edges) {
|
|
221
|
+
const keys = edgeUniqueKeys(coveringEdgeUniqueTuples(edge.declaration, resolve), {
|
|
222
|
+
source: edge.source.key,
|
|
223
|
+
target: edge.target.key,
|
|
224
|
+
props: edge.value.properties,
|
|
225
|
+
});
|
|
226
|
+
const previous = keys.map((key) => holders.get(key)).find((pointer) => pointer !== undefined);
|
|
227
|
+
if (previous !== undefined) {
|
|
228
|
+
diagnostics.push(diagnostic('DV_CORE_EDGE_UNIQUE', edge.pointer, `Core Edge holds a key the Core Edge at ${previous} holds.`));
|
|
229
|
+
}
|
|
230
|
+
for (const key of keys)
|
|
231
|
+
if (!holders.has(key))
|
|
232
|
+
holders.set(key, edge.pointer);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
185
235
|
function validateCardinality(environment, declaration, edges, nodesByRef, diagnostics) {
|
|
186
236
|
const subjects = cardinalitySubjects(edges, nodesByRef);
|
|
187
237
|
if (declaration.orientation === 'undirected') {
|
|
@@ -83,6 +83,7 @@ const coreEndpointRefSchema = discriminatedUnion('kind', [
|
|
|
83
83
|
const edgeEndpointBaseShape = {
|
|
84
84
|
role: memberNameSchema,
|
|
85
85
|
accepts: uniqueArray(classRefSchema).min(1),
|
|
86
|
+
onDelete: enumeration(['restrict', 'detach']).optional(),
|
|
86
87
|
};
|
|
87
88
|
const edgeCountSchema = enumeration(['0..*', '1..*', '0..1', '1']);
|
|
88
89
|
const directedEdgeEndpointsSchema = strictObject({
|
|
@@ -98,6 +99,21 @@ const edgeConstraintsSchema = strictObject({
|
|
|
98
99
|
noSelf: literal(true).optional(),
|
|
99
100
|
acyclic: literal(true).optional(),
|
|
100
101
|
});
|
|
102
|
+
// A tuple names distinct fields: endpoints, the slug and Properties by key. Normalization puts
|
|
103
|
+
// them, and the tuples, in canonical order (`compareEdgeUniqueFields`). No tuple is spelled by the
|
|
104
|
+
// absence of the field, never by an empty list: one spelling per meaning, one revision.
|
|
105
|
+
const edgeUniqueFieldSchema = union([
|
|
106
|
+
literal('source'),
|
|
107
|
+
literal('target'),
|
|
108
|
+
literal('slug'),
|
|
109
|
+
strictObject({ property: propertyKeySchema }),
|
|
110
|
+
]);
|
|
111
|
+
const edgeUniqueSchema = uniqueArray(uniqueArray(edgeUniqueFieldSchema).min(1), (tuple) =>
|
|
112
|
+
// Two spellings of one set of fields are one tuple, repeated.
|
|
113
|
+
JSON.stringify(tuple.map(canonicalJson).sort())).min(1);
|
|
114
|
+
// A Node tuple names distinct Properties by key, which normalization puts in code-point order, as
|
|
115
|
+
// it does the tuples (`compareNodeUniqueTuples`). Two orders of one set of keys are one tuple.
|
|
116
|
+
const nodeUniqueSchema = uniqueArray(uniqueArray(propertyKeySchema).min(1), (tuple) => JSON.stringify([...tuple].sort())).min(1);
|
|
101
117
|
const policyPatternSubjectSchema = strictObject({ kind: literal('subject') });
|
|
102
118
|
const policyPatternObjectSchema = strictObject({ kind: literal('object') });
|
|
103
119
|
const policyPatternSourceSchema = strictObject({ kind: literal('source') });
|
|
@@ -195,11 +211,16 @@ const valueFunctionInputSchema = jsonSchemaSchema.refine((value) => typeof value
|
|
|
195
211
|
const callableExecutionSchema = strictObject({
|
|
196
212
|
durable: boolean(),
|
|
197
213
|
});
|
|
214
|
+
// Its range is language meaning, refused by callable validation with DV_CALLABLE_ADMISSION.
|
|
215
|
+
const callableAdmissionSchema = strictObject({
|
|
216
|
+
maxAge: number(),
|
|
217
|
+
});
|
|
198
218
|
const callableShape = {
|
|
199
219
|
description: descriptionSchema.optional(),
|
|
200
220
|
input: jsonSchemaSchema,
|
|
201
221
|
output: callableOutputSchema,
|
|
202
222
|
execution: callableExecutionSchema.optional(),
|
|
223
|
+
admission: callableAdmissionSchema.optional(),
|
|
203
224
|
auth: enumeration(['anonymous', 'authenticated', 'authorized']),
|
|
204
225
|
policy: union([literal(true), policyCheckExpressionSchema]).optional(),
|
|
205
226
|
};
|
|
@@ -228,10 +249,13 @@ const edgeClassPoliciesSchema = strictObject({
|
|
|
228
249
|
read: classPolicySchema.optional(),
|
|
229
250
|
traverse: classPolicySchema.optional(),
|
|
230
251
|
});
|
|
252
|
+
// The default `'internal'` is the absence of the field: one spelling per meaning, one revision.
|
|
253
|
+
const inheritanceSchema = enumeration(['open', 'final']);
|
|
231
254
|
const nodeClassSchema = strictObject({
|
|
232
255
|
ref: classRefSchema,
|
|
233
256
|
kind: literal('node'),
|
|
234
257
|
abstract: boolean(),
|
|
258
|
+
inheritance: inheritanceSchema.optional(),
|
|
235
259
|
description: descriptionSchema.optional(),
|
|
236
260
|
extends: uniqueArray(classRefSchema),
|
|
237
261
|
properties: propertyRecordSchema,
|
|
@@ -239,15 +263,18 @@ const nodeClassSchema = strictObject({
|
|
|
239
263
|
data: dataDeclarationSchema.optional(),
|
|
240
264
|
icon: iconSchema.optional(),
|
|
241
265
|
policies: nodeClassPoliciesSchema,
|
|
266
|
+
unique: nodeUniqueSchema.optional(),
|
|
242
267
|
});
|
|
243
268
|
const edgeClassShape = {
|
|
244
269
|
ref: classRefSchema,
|
|
245
270
|
kind: literal('edge'),
|
|
246
271
|
abstract: boolean(),
|
|
272
|
+
inheritance: inheritanceSchema.optional(),
|
|
247
273
|
description: descriptionSchema.optional(),
|
|
248
274
|
extends: uniqueArray(classRefSchema),
|
|
249
275
|
properties: propertyRecordSchema,
|
|
250
276
|
constraints: edgeConstraintsSchema,
|
|
277
|
+
unique: edgeUniqueSchema.optional(),
|
|
251
278
|
};
|
|
252
279
|
const edgeClassSchema = strictObject({
|
|
253
280
|
...edgeClassShape,
|
|
@@ -257,14 +284,22 @@ const edgeClassSchema = strictObject({
|
|
|
257
284
|
}).superRefine(validateEdgeOrientationShape);
|
|
258
285
|
const classDeclarationSchema = discriminatedUnion('kind', [nodeClassSchema, edgeClassSchema]);
|
|
259
286
|
function validateEdgeOrientationShape(value, context) {
|
|
260
|
-
if ((value.orientation === 'undirected')
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
287
|
+
if ((value.orientation === 'undirected') !== Array.isArray(value.endpoints)) {
|
|
288
|
+
context.addIssue({
|
|
289
|
+
code: 'custom',
|
|
290
|
+
path: ['endpoints'],
|
|
291
|
+
message: `${value.orientation} Edge endpoints have the wrong structural shape.`,
|
|
292
|
+
params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (value.orientation === 'undirected' && value.unique !== undefined) {
|
|
296
|
+
context.addIssue({
|
|
297
|
+
code: 'custom',
|
|
298
|
+
path: ['unique'],
|
|
299
|
+
message: 'Only a directed Edge Class declares unique tuples.',
|
|
300
|
+
params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
|
|
301
|
+
});
|
|
302
|
+
}
|
|
268
303
|
}
|
|
269
304
|
const functionDefinitionSchema = strictObject({
|
|
270
305
|
ref: functionRefSchema,
|
|
@@ -368,11 +403,11 @@ function isStateTransitions(input) {
|
|
|
368
403
|
function isRecord(input) {
|
|
369
404
|
return input !== null && typeof input === 'object' && !Array.isArray(input);
|
|
370
405
|
}
|
|
371
|
-
function uniqueArray(element) {
|
|
406
|
+
function uniqueArray(element, keyOf = canonicalJson) {
|
|
372
407
|
return array(element).superRefine((values, context) => {
|
|
373
408
|
const firstIndexByValue = new Map();
|
|
374
409
|
values.forEach((value, index) => {
|
|
375
|
-
const key =
|
|
410
|
+
const key = keyOf(value);
|
|
376
411
|
const firstIndex = firstIndexByValue.get(key);
|
|
377
412
|
if (firstIndex === undefined) {
|
|
378
413
|
firstIndexByValue.set(key, index);
|
|
@@ -28,11 +28,6 @@ function validateClassPolicyUses(context, typing, operation) {
|
|
|
28
28
|
continue;
|
|
29
29
|
const pointer = `/classes/${escapePointer(name)}/policies/${operation}`;
|
|
30
30
|
if (isClassRuleChecks(ref)) {
|
|
31
|
-
// A rule lifts the Class ceiling the way a named Policy does, so, as a Policy connects its
|
|
32
|
-
// subject, each way the rule holds must pass through a check of the caller.
|
|
33
|
-
if (!checksNameTheCaller(ref)) {
|
|
34
|
-
context.add('PL_CONNECTIVITY', pointer, `Every branch of the ${operation} rule of Node Class ${name} must check a Policy: sameNode alone names no caller.`);
|
|
35
|
-
}
|
|
36
31
|
// Structure admits Policy checks only on a Node Class: `self` is the observed exact Class.
|
|
37
32
|
validateChecks(context, typing, ref, {
|
|
38
33
|
site: 'class',
|
|
@@ -107,6 +102,11 @@ function validateDeclaredMethods(context, typing, namespace, name, methods, rece
|
|
|
107
102
|
}
|
|
108
103
|
}
|
|
109
104
|
function validateChecks(validation, typing, expression, context) {
|
|
105
|
+
// Checks decide the caller the way a named Policy does, so, as a Policy connects its subject,
|
|
106
|
+
// each way they hold must pass through a check of the caller.
|
|
107
|
+
if (!checksNameTheCaller(expression)) {
|
|
108
|
+
validation.add('PL_CONNECTIVITY', context.pointer, `${SITE[context.site].checks} must check a Policy on every branch: sameNode alone names no caller.`);
|
|
109
|
+
}
|
|
110
110
|
const normalized = normalizePolicyChecks(expression);
|
|
111
111
|
const measure = measureChecks(normalized);
|
|
112
112
|
if (measure.depth > MAX_CHECK_DEPTH ||
|
|
@@ -5,3 +5,8 @@ export declare function serialize(input: unknown, options?: Options): string;
|
|
|
5
5
|
/** Serialize a trusted portable value whose default structural admission proof is still valid. */
|
|
6
6
|
export declare function serializeAdmitted(input: Value): string;
|
|
7
7
|
export declare function encode(input: unknown, options?: Options): Uint8Array;
|
|
8
|
+
/**
|
|
9
|
+
* The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`: an
|
|
10
|
+
* admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
|
|
11
|
+
*/
|
|
12
|
+
export declare function byteLength(input: Value): number;
|
package/dist/value/canonical.js
CHANGED
|
@@ -31,6 +31,18 @@ function serializeSnapshot(input, maxBytes) {
|
|
|
31
31
|
export function encode(input, options) {
|
|
32
32
|
return encoder.encode(serialize(input, options));
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`: an
|
|
36
|
+
* admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
|
|
37
|
+
*/
|
|
38
|
+
export function byteLength(input) {
|
|
39
|
+
if (input !== null && typeof input === 'object') {
|
|
40
|
+
const bytes = immutableSnapshotBytes(input);
|
|
41
|
+
if (bytes !== undefined)
|
|
42
|
+
return bytes;
|
|
43
|
+
}
|
|
44
|
+
return utf8Length(serializeAdmitted(input));
|
|
45
|
+
}
|
|
34
46
|
function serializeValue(value) {
|
|
35
47
|
if (value === null)
|
|
36
48
|
return 'null';
|
package/dist/value/json.d.ts
CHANGED
package/dist/value/json.js
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { CoreContent } from '../../language/core/model/core.js';
|
|
2
|
-
import type { DomainSchemaLookup } from '../../schema/dependency/lookup.js';
|
|
3
|
-
import type { DomainSchemaIR } from '../../schema/model/schema.js';
|
|
4
|
-
import type { UnqualifiedCoreContent } from './core.js';
|
|
5
|
-
type UnqualifiedDomainSchema = Omit<DomainSchemaIR, 'core'> & {
|
|
6
|
-
readonly core: UnqualifiedCoreContent;
|
|
7
|
-
};
|
|
8
|
-
/** Lower rich builder shorthand to the canonical full-key Core representation. */
|
|
9
|
-
export declare function canonicalizeCorePropertyKeys(candidate: UnqualifiedDomainSchema, lookup: DomainSchemaLookup): CoreContent;
|
|
10
|
-
export {};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { PropertyKey as admitPropertyKey } from '../../addressing/index.js';
|
|
2
|
-
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
|
-
import { createPropertyValues } from '../../language/class/property-values.js';
|
|
4
|
-
import { analyzeDefinitions } from '../../schema/analysis/class.js';
|
|
5
|
-
import { buildDomainResolutionContext } from '../../schema/resolution/context.js';
|
|
6
|
-
import { escapeToken, fail } from '../schema/diagnostic.js';
|
|
7
|
-
/** Lower rich builder shorthand to the canonical full-key Core representation. */
|
|
8
|
-
export function canonicalizeCorePropertyKeys(candidate, lookup) {
|
|
9
|
-
// Core Property values do not participate in Class composition. Analyze the
|
|
10
|
-
// already-canonical Definition surface, then admit Core values below.
|
|
11
|
-
const analysisCandidate = {
|
|
12
|
-
...candidate,
|
|
13
|
-
core: { nodes: {}, edges: [] },
|
|
14
|
-
};
|
|
15
|
-
const definitions = analyzeDefinitions(buildDomainResolutionContext(analysisCandidate, lookup)).definitions;
|
|
16
|
-
const nodes = Object.fromEntries(Object.entries(candidate.core.nodes).map(([slug, node]) => {
|
|
17
|
-
const definition = definitions.get(definitionRefIndex(node.class));
|
|
18
|
-
if (definition === undefined) {
|
|
19
|
-
fail('BUILDER_CORE_PROPERTY', `/core/nodes/${escapeToken(slug)}/class`, 'Core property shorthand requires a resolvable concrete Class.');
|
|
20
|
-
}
|
|
21
|
-
return [
|
|
22
|
-
slug,
|
|
23
|
-
{
|
|
24
|
-
...node,
|
|
25
|
-
properties: qualify(node.properties, definition, `/core/nodes/${escapeToken(slug)}/properties`),
|
|
26
|
-
},
|
|
27
|
-
];
|
|
28
|
-
}));
|
|
29
|
-
const edges = candidate.core.edges.map((edge, index) => {
|
|
30
|
-
const definition = definitions.get(definitionRefIndex(edge.class));
|
|
31
|
-
if (definition === undefined) {
|
|
32
|
-
fail('BUILDER_CORE_PROPERTY', `/core/edges/${index}/class`, 'Core property shorthand requires a resolvable concrete Class.');
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
...edge,
|
|
36
|
-
properties: qualify(edge.properties, definition, `/core/edges/${index}/properties`),
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
return { nodes, edges };
|
|
40
|
-
}
|
|
41
|
-
function qualify(input, definition, pointer) {
|
|
42
|
-
const byName = new Map();
|
|
43
|
-
for (const [key, field] of definition.properties) {
|
|
44
|
-
const candidates = byName.get(field.name) ?? [];
|
|
45
|
-
candidates.push(key);
|
|
46
|
-
byName.set(field.name, candidates);
|
|
47
|
-
}
|
|
48
|
-
const result = {};
|
|
49
|
-
const sourceByKey = new Map();
|
|
50
|
-
for (const [inputKey, value] of Object.entries(input)) {
|
|
51
|
-
const key = resolveInputKey(inputKey, definition, byName, `${pointer}/${escapeToken(inputKey)}`);
|
|
52
|
-
const previous = sourceByKey.get(key);
|
|
53
|
-
if (previous !== undefined) {
|
|
54
|
-
fail('BUILDER_CORE_PROPERTY', `${pointer}/${escapeToken(inputKey)}`, `Property is supplied twice through ${JSON.stringify(previous)} and ${JSON.stringify(inputKey)}.`);
|
|
55
|
-
}
|
|
56
|
-
sourceByKey.set(key, inputKey);
|
|
57
|
-
result[key] = value;
|
|
58
|
-
}
|
|
59
|
-
return createPropertyValues(Object.entries(result).map(([key, value]) => [admitPropertyKey(key), value]));
|
|
60
|
-
}
|
|
61
|
-
function resolveInputKey(input, definition, byName, pointer) {
|
|
62
|
-
if (admitPropertyKey.is(input)) {
|
|
63
|
-
const key = admitPropertyKey(input);
|
|
64
|
-
if (!definition.properties.has(key)) {
|
|
65
|
-
fail('BUILDER_CORE_PROPERTY', pointer, `Property ${input} is not present on the Core Class.`);
|
|
66
|
-
}
|
|
67
|
-
return key;
|
|
68
|
-
}
|
|
69
|
-
const candidates = byName.get(input) ?? [];
|
|
70
|
-
if (candidates.length === 1)
|
|
71
|
-
return candidates[0];
|
|
72
|
-
if (candidates.length === 0) {
|
|
73
|
-
fail('BUILDER_CORE_PROPERTY', pointer, `Property ${JSON.stringify(input)} does not exist.`);
|
|
74
|
-
}
|
|
75
|
-
fail('BUILDER_CORE_PROPERTY', pointer, `Property ${JSON.stringify(input)} is ambiguous; use one of: ${candidates.join(', ')}.`);
|
|
76
|
-
}
|