@astrale-os/kernel-dsl 1.0.0-beta.13 → 1.0.0-beta.15
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/domain-dependency.d.ts +0 -6
- package/dist/v1/addressing/domain-dependency.js +1 -6
- package/dist/v1/addressing/key.js +2 -1
- package/dist/v1/addressing/patterns.d.ts +1 -0
- package/dist/v1/addressing/patterns.js +2 -1
- package/dist/v1/builder/class/authoring.d.ts +43 -0
- package/dist/v1/builder/class/property.d.ts +3 -9
- package/dist/v1/builder/class/property.js +1 -0
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/class.js +5 -1
- package/dist/v1/builder/lowering/policy.js +2 -0
- package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
- package/dist/v1/builder/lowering/schema/context.js +22 -0
- package/dist/v1/builder/policy/authoring.d.ts +84 -4
- package/dist/v1/builder/policy/authoring.js +44 -14
- package/dist/v1/builder/schema/projection/class.d.ts +116 -14
- package/dist/v1/bundle/codec.js +9 -9
- package/dist/v1/bundle/diagnostic.d.ts +2 -0
- package/dist/v1/bundle/diagnostic.js +6 -0
- package/dist/v1/bundle/structure.js +2 -2
- package/dist/v1/compiled/admission/accept.js +3 -3
- package/dist/v1/compiled/admission/closure.js +2 -2
- package/dist/v1/compiled/admission/semantics/core.js +3 -7
- package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
- package/dist/v1/domain/validation/create.d.ts +4 -1
- package/dist/v1/domain/validation/create.js +21 -34
- package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
- package/dist/v1/language/class/model/class.d.ts +4 -4
- package/dist/v1/language/class/model/edge.d.ts +2 -2
- package/dist/v1/language/class/model/property.d.ts +16 -7
- package/dist/v1/language/core/facts.d.ts +17 -4
- package/dist/v1/language/core/facts.js +26 -5
- package/dist/v1/language/definition/metadata.d.ts +5 -0
- package/dist/v1/language/definition/metadata.js +7 -0
- package/dist/v1/language/limits.d.ts +64 -0
- package/dist/v1/language/limits.js +60 -0
- package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
- package/dist/v1/language/policy/analysis/budgets.js +30 -20
- package/dist/v1/language/policy/analysis/checks.js +3 -2
- package/dist/v1/language/policy/analysis/typing.js +4 -3
- package/dist/v1/language/policy/model/policy.d.ts +5 -5
- package/dist/v1/language/policy/normalization/normalize.js +17 -16
- package/dist/v1/language/validation/diagnostic.d.ts +1 -1
- package/dist/v1/language/validation/rule.d.ts +29 -0
- package/dist/v1/language/validation/rule.js +120 -0
- package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
- package/dist/v1/language/value/profile/rules.js +13 -37
- package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
- package/dist/v1/language/value/profile/vocabulary.js +52 -5
- package/dist/v1/language/value/reference/registry.index.js +2 -1
- package/dist/v1/language/value/regexp/ast.js +6 -7
- package/dist/v1/language/value/regexp/compile.d.ts +5 -11
- package/dist/v1/language/value/regexp/compile.js +14 -12
- package/dist/v1/language/value/regexp/syntax.js +3 -2
- package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
- package/dist/v1/language/value/syntax/keywords.js +4 -20
- package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
- package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
- package/dist/v1/schema/admission/accept.js +3 -14
- package/dist/v1/schema/admission/callable.d.ts +21 -0
- package/dist/v1/schema/admission/callable.js +96 -0
- package/dist/v1/schema/analysis/class.js +33 -15
- package/dist/v1/schema/compatibility/api.d.ts +1 -1
- package/dist/v1/schema/compatibility/coordinate.js +3 -3
- package/dist/v1/schema/compatibility/model.d.ts +5 -0
- package/dist/v1/schema/diagnostic.d.ts +17 -3
- package/dist/v1/schema/diagnostic.js +11 -0
- package/dist/v1/schema/index.d.ts +10 -1
- package/dist/v1/schema/index.js +4 -0
- package/dist/v1/schema/model/schema.d.ts +19 -1
- package/dist/v1/schema/normalization/domain.d.ts +2 -0
- package/dist/v1/schema/normalization/domain.js +2 -1
- package/dist/v1/schema/resolution/closure.js +2 -1
- package/dist/v1/schema/resolution/context.js +10 -8
- package/dist/v1/schema/resolution/domain.d.ts +5 -0
- package/dist/v1/schema/resolution/domain.js +1 -1
- package/dist/v1/schema/rule.d.ts +32 -0
- package/dist/v1/schema/rule.js +428 -0
- package/dist/v1/schema/validation/callable.d.ts +35 -2
- package/dist/v1/schema/validation/callable.js +51 -8
- package/dist/v1/schema/validation/class.js +36 -44
- package/dist/v1/schema/validation/core.js +19 -13
- package/dist/v1/schema/validation/domain.js +2 -2
- package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
- package/dist/v1/schema/validation/domain.json-schema.js +183 -0
- package/dist/v1/schema/validation/domain.route.d.ts +6 -1
- package/dist/v1/schema/validation/domain.route.js +18 -0
- package/dist/v1/schema/validation/domain.structure.d.ts +4020 -0
- package/dist/v1/schema/validation/domain.structure.js +359 -153
- package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
- package/dist/v1/schema/validation/json.json-schema.js +36 -0
- package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
- package/dist/v1/schema/validation/policy/budgets.js +89 -16
- package/dist/v1/schema/validation/policy/references.js +6 -21
- package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
- package/dist/v1/schema/validation/policy/uses.js +28 -51
- package/dist/v1/schema/validation/value-api.d.ts +12 -1
- package/dist/v1/schema/validation/value-api.js +10 -1
- package/dist/v1/schema/validation/value.d.ts +3 -0
- package/dist/v1/schema/validation/value.js +6 -12
- package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
- package/dist/v1/schema/validation/value.json-schema.js +126 -0
- package/package.json +3 -1
|
@@ -30,27 +30,25 @@ export function validateDefinitions(environment, analysis) {
|
|
|
30
30
|
validateFacet(environment, declaration, pointer, diagnostics);
|
|
31
31
|
validateParents(environment, ref, declaration.kind, declaration.extends, diagnostics);
|
|
32
32
|
if (declaration.kind === 'edge') {
|
|
33
|
-
validateEdgeContract(environment, ref, declaration, diagnostics);
|
|
33
|
+
validateEdgeContract(environment, ref, declaration, pointer, diagnostics);
|
|
34
34
|
scope ??= lifetimeScope([...environment.definitions.values()].map(({ declaration }) => declaration));
|
|
35
35
|
validateEdgeLifetimes(scope, ref, declaration, pointer, diagnostics);
|
|
36
|
+
validateEdgeClassComposition(environment, ref, declaration, pointer, diagnostics);
|
|
36
37
|
}
|
|
37
38
|
validateUniqueProperties(environment, analysis, ref, declaration, pointer, diagnostics);
|
|
38
39
|
}
|
|
39
|
-
validateEdgeClassComposition(environment, diagnostics);
|
|
40
40
|
return Object.freeze(diagnostics);
|
|
41
41
|
}
|
|
42
42
|
function validateFacet(environment, declaration, pointer, diagnostics) {
|
|
43
43
|
const valueRegistry = createValueSchemaRegistry(environment);
|
|
44
44
|
for (const [name, property] of Object.entries(declaration.properties)) {
|
|
45
45
|
const propertyPointer = `${pointer}/properties/${escapePointerToken(name)}`;
|
|
46
|
+
// The structure refuses a blob on an Edge Property and a State Property on an Edge Class.
|
|
46
47
|
if (isBlobValueSchema(property.schema) &&
|
|
47
|
-
(
|
|
48
|
+
(property.auto !== undefined || property.state !== undefined)) {
|
|
48
49
|
diagnostics.push(diagnostic('DV_VALUE_SCHEMA', `${propertyPointer}/schema/x-astrale-value-kind`, 'Blob values are allowed only on non-automatic, non-state Node properties.'));
|
|
49
50
|
}
|
|
50
51
|
if (property.state !== undefined) {
|
|
51
|
-
if (declaration.kind !== 'node') {
|
|
52
|
-
diagnostics.push(diagnostic('DV_PROPERTY_STATE', `${propertyPointer}/state`, 'A State Property may be declared only on a Node Class.'));
|
|
53
|
-
}
|
|
54
52
|
try {
|
|
55
53
|
const topology = admitStateTopology(property.state);
|
|
56
54
|
const expected = Object.keys(topology.transitions).sort();
|
|
@@ -122,23 +120,26 @@ function validateParents(environment, owner, ownerKind, parents, diagnostics) {
|
|
|
122
120
|
}
|
|
123
121
|
}
|
|
124
122
|
}
|
|
125
|
-
function validateEdgeContract(environment, owner, declaration, diagnostics) {
|
|
126
|
-
const
|
|
123
|
+
function validateEdgeContract(environment, owner, declaration, pointer, diagnostics) {
|
|
124
|
+
const directed = declaration.orientation === 'directed';
|
|
125
|
+
const endpoints = directed
|
|
127
126
|
? [declaration.endpoints.source, declaration.endpoints.target]
|
|
128
127
|
: declaration.endpoints;
|
|
128
|
+
const keys = directed ? ['source', 'target'] : ['0', '1'];
|
|
129
129
|
if (endpoints[0].role === endpoints[1].role) {
|
|
130
|
-
diagnostics.push(diagnostic('DV_EDGE_ROLE',
|
|
131
|
-
}
|
|
132
|
-
const counts = declaration.orientation === 'directed'
|
|
133
|
-
? [declaration.endpoints.source.outgoing, declaration.endpoints.target.incoming]
|
|
134
|
-
: [declaration.endpoints[0].incident, declaration.endpoints[1].incident];
|
|
135
|
-
validateEndpoint(environment, owner, endpoints[0], counts[0], declaration.orientation === 'directed' ? 'source' : 'first endpoint', diagnostics);
|
|
136
|
-
validateEndpoint(environment, owner, endpoints[1], counts[1], declaration.orientation === 'directed' ? 'target' : 'second endpoint', diagnostics);
|
|
137
|
-
if (declaration.orientation === 'undirected') {
|
|
138
|
-
if (declaration.constraints.acyclic === true) {
|
|
139
|
-
diagnostics.push(diagnostic('DV_EDGE_CONSTRAINT', '', 'acyclic is valid only for directed Edge contracts.'));
|
|
140
|
-
}
|
|
130
|
+
diagnostics.push(diagnostic('DV_EDGE_ROLE', `${pointer}/endpoints/${keys[1]}/role`, `${formatDefinitionRef(owner)} has equal endpoint roles.`));
|
|
141
131
|
}
|
|
132
|
+
const counts = directed
|
|
133
|
+
? [
|
|
134
|
+
['outgoing', declaration.endpoints.source.outgoing],
|
|
135
|
+
['incoming', declaration.endpoints.target.incoming],
|
|
136
|
+
]
|
|
137
|
+
: [
|
|
138
|
+
['incident', declaration.endpoints[0].incident],
|
|
139
|
+
['incident', declaration.endpoints[1].incident],
|
|
140
|
+
];
|
|
141
|
+
validateEndpoint(environment, owner, endpoints[0], counts[0], directed ? 'source' : 'first endpoint', `${pointer}/endpoints/${keys[0]}`, diagnostics);
|
|
142
|
+
validateEndpoint(environment, owner, endpoints[1], counts[1], directed ? 'target' : 'second endpoint', `${pointer}/endpoints/${keys[1]}`, diagnostics);
|
|
142
143
|
}
|
|
143
144
|
/**
|
|
144
145
|
* Each Property a key tuple names is one of the Class's own or inherited Properties, and keyable:
|
|
@@ -190,25 +191,20 @@ function validateUniqueProperties(environment, analysis, owner, declaration, poi
|
|
|
190
191
|
check(field.property, `${pointer}/unique/${t}/${f}/property`);
|
|
191
192
|
}));
|
|
192
193
|
}
|
|
193
|
-
|
|
194
|
+
// The structure refuses a repeated accepted Class before semantics: `accepts` is a set.
|
|
195
|
+
function validateEndpoint(environment, owner, endpoint, [countField, count], side, pointer, diagnostics) {
|
|
194
196
|
// An Edge Class binds only the Nodes of its Domain and its dependents: it cannot oblige the
|
|
195
197
|
// Nodes of a Class another Domain owns to carry its Edges.
|
|
196
198
|
const foreign = endpoint.accepts.find((ref) => ref.origin !== owner.origin);
|
|
197
199
|
if (foreign !== undefined && (count === '1' || count === '1..*')) {
|
|
198
|
-
diagnostics.push(diagnostic('DV_EDGE_FOREIGN_MINIMUM',
|
|
200
|
+
diagnostics.push(diagnostic('DV_EDGE_FOREIGN_MINIMUM', `${pointer}/${countField}`, `${formatDefinitionRef(owner)} ${side} accepts ${formatDefinitionRef(foreign)} of another Domain and cannot require ${count} Edges.`));
|
|
199
201
|
}
|
|
200
|
-
const
|
|
201
|
-
for (const ref of endpoint.accepts) {
|
|
202
|
-
const key = definitionRefIndex(ref);
|
|
203
|
-
if (seen.has(key)) {
|
|
204
|
-
diagnostics.push(diagnostic('DV_EDGE_ACCEPTS', '', `${formatDefinitionRef(owner)} ${side} repeats ${formatDefinitionRef(ref)}.`));
|
|
205
|
-
}
|
|
206
|
-
seen.add(key);
|
|
202
|
+
for (const [index, ref] of endpoint.accepts.entries()) {
|
|
207
203
|
const resolved = environment.resolve(ref);
|
|
208
204
|
if (resolved === undefined ||
|
|
209
205
|
resolved.ref.kind !== 'class' ||
|
|
210
206
|
resolved.declaration.kind !== 'node') {
|
|
211
|
-
diagnostics.push(diagnostic('DV_EDGE_ACCEPTS',
|
|
207
|
+
diagnostics.push(diagnostic('DV_EDGE_ACCEPTS', `${pointer}/accepts/${index}`, `${formatDefinitionRef(owner)} ${side} ref ${formatDefinitionRef(ref)} does not resolve to a Node Class.`));
|
|
212
208
|
}
|
|
213
209
|
}
|
|
214
210
|
}
|
|
@@ -225,22 +221,18 @@ function validateEdgeLifetimes(scope, owner, declaration, pointer, diagnostics)
|
|
|
225
221
|
diagnostics.push(diagnostic('DV_EDGE_LIFETIME', `${pointer}/endpoints/${keys[index]}/onDelete`, `${formatDefinitionRef(owner)} ${sides[index]} ${reason}`));
|
|
226
222
|
}
|
|
227
223
|
}
|
|
228
|
-
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Edge inheritance is refinement, not a second interface composition mechanism. A dependency
|
|
226
|
+
* Edge Class was judged against this same exact closure when it was accepted.
|
|
227
|
+
*/
|
|
228
|
+
function validateEdgeClassComposition(environment, owner, child, pointer, diagnostics) {
|
|
229
|
+
for (const [index, parentRef] of child.extends.entries()) {
|
|
230
|
+
const parent = environment.resolveAny(parentRef);
|
|
231
|
+
if (parent === undefined || parent.ref.kind !== 'class' || parent.declaration.kind !== 'edge') {
|
|
232
232
|
continue;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (parent === undefined ||
|
|
237
|
-
parent.ref.kind !== 'class' ||
|
|
238
|
-
parent.declaration.kind !== 'edge') {
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
if (!edgeContractRefines(environment, child, parent.declaration)) {
|
|
242
|
-
diagnostics.push(diagnostic('DV_EDGE_CLASS_COMPOSITION', '', `${formatDefinitionRef(resolved.ref)} does not safely refine ${formatDefinitionRef(parentRef)}.`));
|
|
243
|
-
}
|
|
233
|
+
}
|
|
234
|
+
if (!edgeContractRefines(environment, child, parent.declaration)) {
|
|
235
|
+
diagnostics.push(diagnostic('DV_EDGE_CLASS_COMPOSITION', `${pointer}/extends/${index}`, `${formatDefinitionRef(owner)} does not safely refine ${formatDefinitionRef(parentRef)}.`));
|
|
244
236
|
}
|
|
245
237
|
}
|
|
246
238
|
}
|
|
@@ -3,11 +3,11 @@ import { Key } from '../../addressing/key.js';
|
|
|
3
3
|
import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
|
|
4
4
|
import { isMethodExecutable } from '../../language/callable/executable.js';
|
|
5
5
|
import { coveringEdgeUniqueTuples, coveringNodeUniqueTuples, edgeUniqueKeys, nodeUniqueKeys, } from '../../language/class/unique.js';
|
|
6
|
-
import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, } from '../../language/core/facts.js';
|
|
6
|
+
import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, DOMAIN_ENDPOINT_CLASS, } from '../../language/core/facts.js';
|
|
7
7
|
import { policyObjectKey, policyObjectOrigin, projectedMemberClass, } from '../../language/policy/model/policy.js';
|
|
8
8
|
import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
|
|
9
9
|
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
10
|
-
import { diagnostic } from '../diagnostic.js';
|
|
10
|
+
import { diagnostic, valueDiagnostic } from '../diagnostic.js';
|
|
11
11
|
import { classSatisfies } from './class.js';
|
|
12
12
|
const formatDefinitionRef = Key.of;
|
|
13
13
|
export function validateCore(environment, analysis, registry) {
|
|
@@ -83,7 +83,9 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
|
|
|
83
83
|
continue;
|
|
84
84
|
const endpointsSatisfy = coreEndpointsSatisfy(declaration, source, target, (endpoint, accepts) => endpointSatisfies(environment, endpoint, accepts));
|
|
85
85
|
if (!endpointsSatisfy) {
|
|
86
|
-
diagnostics.push(diagnostic('DV_CORE_EDGE_CLASS', pointer, `Core Edge endpoints do not satisfy ${formatDefinitionRef(edge.class)}
|
|
86
|
+
diagnostics.push(diagnostic('DV_CORE_EDGE_CLASS', pointer, `Core Edge endpoints do not satisfy ${formatDefinitionRef(edge.class)}.${source.kind === 'domain' || target.kind === 'domain'
|
|
87
|
+
? ` The owning Domain is a ${formatDefinitionRef(DOMAIN_ENDPOINT_CLASS)} Node: the endpoint that names it must accept that Class or one it extends.`
|
|
88
|
+
: ''}`, formatDefinitionRef(edge.class)));
|
|
87
89
|
continue;
|
|
88
90
|
}
|
|
89
91
|
edges.push({ source, target, declaration, value: edge, pointer });
|
|
@@ -92,7 +94,7 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
|
|
|
92
94
|
}
|
|
93
95
|
function resolveCoreEndpoint(environment, ref, pointer, nodesByRef, diagnostics) {
|
|
94
96
|
if (ref.kind === 'domain')
|
|
95
|
-
return { kind: 'domain', ref, key: 'domain' };
|
|
97
|
+
return { kind: 'domain', ref, key: 'domain', classRef: DOMAIN_ENDPOINT_CLASS };
|
|
96
98
|
const undeclared = () => {
|
|
97
99
|
const key = policyObjectKey(ref);
|
|
98
100
|
diagnostics.push(diagnostic('DV_CORE_REF', pointer, `Core endpoint ${key} does not name a declared local Definition.`, key));
|
|
@@ -122,10 +124,14 @@ function declaresProjectedMember(environment, ref) {
|
|
|
122
124
|
return Object.hasOwn(candidate.policies, ref.name);
|
|
123
125
|
return Object.hasOwn(candidate.views, ref.name);
|
|
124
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* A Core Node is admitted by its own authored Class, a projected member by the Kernel declaration
|
|
129
|
+
* Class it projects as, and the owning Domain by the Kernel `Domain` Class of its Node: the write
|
|
130
|
+
* that projects the Edge binds exactly that Node, so an Edge Class that does not accept it can
|
|
131
|
+
* never be installed (DV-CORE-EDGE-CLASS).
|
|
132
|
+
*/
|
|
125
133
|
function endpointSatisfies(environment, endpoint, accepts) {
|
|
126
|
-
|
|
127
|
-
// Its concrete Class contract is therefore admitted by the projection compiler.
|
|
128
|
-
return endpoint.kind === 'domain' || classSatisfies(environment, endpoint.classRef, accepts);
|
|
134
|
+
return classSatisfies(environment, endpoint.classRef, accepts);
|
|
129
135
|
}
|
|
130
136
|
function validateCreationProperties(definition, properties, registry, code, pointer, diagnostics) {
|
|
131
137
|
const schema = {
|
|
@@ -139,8 +145,8 @@ function validateCreationProperties(definition, properties, registry, code, poin
|
|
|
139
145
|
.sort(compareUnicode),
|
|
140
146
|
additionalProperties: false,
|
|
141
147
|
};
|
|
142
|
-
for (const
|
|
143
|
-
diagnostics.push(
|
|
148
|
+
for (const refusal of validateJsonInstance(schema, properties, registry, pointer)) {
|
|
149
|
+
diagnostics.push(valueDiagnostic(code, refusal));
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
152
|
function validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics) {
|
|
@@ -181,11 +187,11 @@ function validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics) {
|
|
|
181
187
|
report: (issue) => diagnostics.push(coreEdgeDiagnostic(issue)),
|
|
182
188
|
});
|
|
183
189
|
}
|
|
184
|
-
/** A projected member's degree is reported at the Edge that names it. */
|
|
190
|
+
/** A projected member's degree, and the owning Domain's, is reported at the Edge that names it. */
|
|
185
191
|
function factEndpoint(endpoint, pointer) {
|
|
186
|
-
return endpoint.kind === '
|
|
187
|
-
?
|
|
188
|
-
: endpoint;
|
|
192
|
+
return endpoint.kind === 'core'
|
|
193
|
+
? endpoint
|
|
194
|
+
: { key: endpoint.key, member: { class: endpoint.classRef, pointer } };
|
|
189
195
|
}
|
|
190
196
|
function coreEdgeDiagnostic(issue) {
|
|
191
197
|
switch (issue.kind) {
|
|
@@ -2,7 +2,7 @@ import { orderAndDeduplicate } from '../../language/validation/diagnostic.js';
|
|
|
2
2
|
import { analyzeDefinitions } from '../analysis/class.js';
|
|
3
3
|
import { buildDomainResolutionContext } from '../resolution/context.js';
|
|
4
4
|
import { createValueSchemaRegistry } from '../resolution/value.registry.js';
|
|
5
|
-
import {
|
|
5
|
+
import { validateContracts } from './callable.js';
|
|
6
6
|
import { validateDefinitions } from './class.js';
|
|
7
7
|
import { validateCore } from './core.js';
|
|
8
8
|
import { validatePolicies } from './policy/validate.js';
|
|
@@ -16,7 +16,7 @@ export function validateDomainSchemaSemantics(schema, lookup) {
|
|
|
16
16
|
...context.diagnostics,
|
|
17
17
|
...validateEmbeddedValueSchemas(schema, registry),
|
|
18
18
|
...validateDefinitions(context, analysis),
|
|
19
|
-
...
|
|
19
|
+
...validateContracts(schema, registry),
|
|
20
20
|
...validatePolicies(context, analysis),
|
|
21
21
|
...validateViews(context),
|
|
22
22
|
...validateCore(context, analysis, registry),
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type core as zodCore } from 'zod';
|
|
2
|
+
export type JsonSchemaDocument = zodCore.JSONSchema.JSONSchema;
|
|
3
|
+
/**
|
|
4
|
+
* The JSON Schema of an authored Domain schema document, rendered from the structure the Kernel
|
|
5
|
+
* enforces. Zod derives what it reads; `documentJsonSchema` states each refinement it cannot read,
|
|
6
|
+
* or names the rule JSON Schema cannot state. A refinement neither stated nor named fails the
|
|
7
|
+
* rendering, so that no check of the structure is dropped silently. Every value schema is in the
|
|
8
|
+
* published dialect and states the profile rules JSON Schema can state; the others are named.
|
|
9
|
+
*/
|
|
10
|
+
export declare function domainSchemaJsonSchema(): JsonSchemaDocument;
|
|
11
|
+
/**
|
|
12
|
+
* Every refinement and custom part of the structure carries its statement in `documentJsonSchema`:
|
|
13
|
+
* the keywords that state it, or its named rule. Only parts zod renders take part.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertChecksStated(schema: zodCore.$ZodType, visited?: Set<zodCore.$ZodType<unknown, unknown, zodCore.$ZodTypeInternals<unknown, unknown>>>): void;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { toJSONSchema } from 'zod';
|
|
2
|
+
import { VALUE_RULES } from '../../language/validation/rule.js';
|
|
3
|
+
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
4
|
+
import { RULES } from '../rule.js';
|
|
5
|
+
import { domainSchemaStructure, documentJsonSchema, structureRules, } from './domain.structure.js';
|
|
6
|
+
import { jsonValueJsonSchema, jsonValueRules } from './json.json-schema.js';
|
|
7
|
+
import { valueSchemaJsonSchema, valueSchemaRules, } from './value.json-schema.js';
|
|
8
|
+
/** The checks the published document names, each with its stage and the positions it constrains. */
|
|
9
|
+
const namedRules = { ...structureRules, ...jsonValueRules, ...valueSchemaRules };
|
|
10
|
+
/**
|
|
11
|
+
* The JSON Schema of an authored Domain schema document, rendered from the structure the Kernel
|
|
12
|
+
* enforces. Zod derives what it reads; `documentJsonSchema` states each refinement it cannot read,
|
|
13
|
+
* or names the rule JSON Schema cannot state. A refinement neither stated nor named fails the
|
|
14
|
+
* rendering, so that no check of the structure is dropped silently. Every value schema is in the
|
|
15
|
+
* published dialect and states the profile rules JSON Schema can state; the others are named.
|
|
16
|
+
*/
|
|
17
|
+
export function domainSchemaJsonSchema() {
|
|
18
|
+
assertChecksStated(domainSchemaStructure);
|
|
19
|
+
const { $schema, $defs = {}, ...root } = toJSONSchema(domainSchemaStructure, {
|
|
20
|
+
metadata: documentJsonSchema,
|
|
21
|
+
io: 'input',
|
|
22
|
+
// Every opaque part is stated or named, as `assertChecksStated` proved.
|
|
23
|
+
unrepresentable: 'any',
|
|
24
|
+
override: ({ zodSchema, jsonSchema: rendered }) => {
|
|
25
|
+
// Zod states the items of a tuple but not its length.
|
|
26
|
+
const def = zodSchema._zod.def;
|
|
27
|
+
if (def.type !== 'tuple')
|
|
28
|
+
return;
|
|
29
|
+
if (def.rest !== null || def.items.some((item) => item._zod.optin === 'optional')) {
|
|
30
|
+
throw new Error('A tuple of the structure has a fixed length.');
|
|
31
|
+
}
|
|
32
|
+
rendered.minItems = def.items.length;
|
|
33
|
+
rendered.maxItems = def.items.length;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const unnamed = Object.keys($defs).filter((name) => name.startsWith('__schema'));
|
|
37
|
+
if (unnamed.length > 0) {
|
|
38
|
+
throw new Error(`Every shared part of the structure is named: ${unnamed.join(', ')} is not.`);
|
|
39
|
+
}
|
|
40
|
+
const json = jsonValueJsonSchema();
|
|
41
|
+
const values = valueSchemaJsonSchema();
|
|
42
|
+
const valueSchema = $defs['value-schema'];
|
|
43
|
+
const nodeProperty = $defs['node-property-definition'];
|
|
44
|
+
if (root.allOf !== undefined ||
|
|
45
|
+
valueSchema === undefined ||
|
|
46
|
+
nodeProperty === undefined ||
|
|
47
|
+
valueSchema.allOf !== undefined ||
|
|
48
|
+
nodeProperty.allOf !== undefined ||
|
|
49
|
+
[...Object.keys(json.definitions), ...Object.keys(values.definitions)].some((name) => name in $defs)) {
|
|
50
|
+
throw new Error('Portable JSON extends the root, and the value language the value-schema and Node Property definitions.');
|
|
51
|
+
}
|
|
52
|
+
const definitions = Object.fromEntries(Object.entries({
|
|
53
|
+
...$defs,
|
|
54
|
+
'value-schema': { ...valueSchema, allOf: [...values.root] },
|
|
55
|
+
'node-property-definition': { ...nodeProperty, allOf: [values.nodeProperty] },
|
|
56
|
+
...json.definitions,
|
|
57
|
+
...values.definitions,
|
|
58
|
+
}).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)));
|
|
59
|
+
const document = { ...root, allOf: [json.root] };
|
|
60
|
+
const positions = rulePositions({ ...document, $defs: definitions });
|
|
61
|
+
return {
|
|
62
|
+
$schema,
|
|
63
|
+
$id: 'https://schemas.astrale.ai/dsl/v1/domain-schema',
|
|
64
|
+
title: 'V1 DomainSchema',
|
|
65
|
+
$comment: 'Generated from the structure the Kernel enforces; value schemas are in the dialect https://schemas.astrale.ai/dsl/v1/value-schema. Each check JSON Schema cannot state is named under x-astrale-rules, with its stage and the positions it constrains.',
|
|
66
|
+
'x-astrale-rules': Object.fromEntries(Object.keys(namedRules).sort().map((name) => {
|
|
67
|
+
const { statement, ...rule } = namedRules[name];
|
|
68
|
+
const named = { ...rule, stage: stageOf(rule), statement };
|
|
69
|
+
return [name, { ...named, positions: positions.get(name) ?? [] }];
|
|
70
|
+
})),
|
|
71
|
+
...document,
|
|
72
|
+
$defs: definitions,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What a named rule reads, in the stages of `schema.RULES`: its code's, or for a value code the
|
|
77
|
+
* value rule's own, which reads the document alone unless it reads the context.
|
|
78
|
+
*/
|
|
79
|
+
function stageOf(rule) {
|
|
80
|
+
if (rule.valueCode === undefined)
|
|
81
|
+
return RULES[rule.code].stage;
|
|
82
|
+
return VALUE_RULES[rule.valueCode].stage === 'context' ? 'context' : 'rule';
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Every refinement and custom part of the structure carries its statement in `documentJsonSchema`:
|
|
86
|
+
* the keywords that state it, or its named rule. Only parts zod renders take part.
|
|
87
|
+
*/
|
|
88
|
+
export function assertChecksStated(schema, visited = new Set()) {
|
|
89
|
+
if (visited.has(schema))
|
|
90
|
+
return;
|
|
91
|
+
visited.add(schema);
|
|
92
|
+
const part = schema;
|
|
93
|
+
const def = part._zod.def;
|
|
94
|
+
for (const check of def.checks ?? []) {
|
|
95
|
+
if (check._zod.def.check === 'custom' && !isStated(schema, check)) {
|
|
96
|
+
throw new Error('A refinement of the structure is neither stated nor named.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
switch (def.type) {
|
|
100
|
+
case 'boolean':
|
|
101
|
+
case 'enum':
|
|
102
|
+
case 'literal':
|
|
103
|
+
case 'null':
|
|
104
|
+
case 'number':
|
|
105
|
+
case 'string':
|
|
106
|
+
return;
|
|
107
|
+
case 'custom':
|
|
108
|
+
if (statementOf(schema) === undefined) {
|
|
109
|
+
throw new Error('A custom part of the structure is neither stated nor named.');
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
case 'array':
|
|
113
|
+
return assertChecksStated(def.element, visited);
|
|
114
|
+
case 'lazy':
|
|
115
|
+
return assertChecksStated(part._zod.innerType, visited);
|
|
116
|
+
case 'object':
|
|
117
|
+
for (const field of Object.values(def.shape))
|
|
118
|
+
assertChecksStated(field, visited);
|
|
119
|
+
return;
|
|
120
|
+
case 'optional':
|
|
121
|
+
return assertChecksStated(def.innerType, visited);
|
|
122
|
+
case 'record':
|
|
123
|
+
assertChecksStated(def.keyType, visited);
|
|
124
|
+
return assertChecksStated(def.valueType, visited);
|
|
125
|
+
case 'tuple':
|
|
126
|
+
for (const item of def.items)
|
|
127
|
+
assertChecksStated(item, visited);
|
|
128
|
+
return;
|
|
129
|
+
case 'union':
|
|
130
|
+
for (const option of def.options)
|
|
131
|
+
assertChecksStated(option, visited);
|
|
132
|
+
return;
|
|
133
|
+
default:
|
|
134
|
+
throw new Error(`The structure has a ${def.type} part, which this rendering does not read.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** A check is stated where it was added, or on a refinement of that schema. */
|
|
138
|
+
function isStated(schema, check) {
|
|
139
|
+
for (let current = schema; current !== undefined;) {
|
|
140
|
+
if (statementOf(current) !== undefined)
|
|
141
|
+
return true;
|
|
142
|
+
const parent = current._zod.parent;
|
|
143
|
+
if (parent?._zod.def.checks?.includes(check) !== true)
|
|
144
|
+
return false;
|
|
145
|
+
current = parent;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
/** The keywords or rule registered on this very schema, which `get` would inherit from a parent. */
|
|
150
|
+
function statementOf(schema) {
|
|
151
|
+
const { id: _id, description: _description, ...statement } = documentJsonSchema._map.get(schema) ?? {};
|
|
152
|
+
return Object.keys(statement).length > 0 ? statement : undefined;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Where each rule applies, as JSON Pointers into the document. The marker a named check leaves is
|
|
156
|
+
* removed; every `maxLength` counts UTF-16 code units, the length the structure measures.
|
|
157
|
+
*/
|
|
158
|
+
function rulePositions(document) {
|
|
159
|
+
const positions = new Map();
|
|
160
|
+
const note = (rule, pointer) => positions.set(rule, [...(positions.get(rule) ?? []), `#${pointer}`]);
|
|
161
|
+
const visit = (value, pointer) => {
|
|
162
|
+
if (Array.isArray(value)) {
|
|
163
|
+
value.forEach((item, index) => visit(item, `${pointer}/${index}`));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (value === null || typeof value !== 'object')
|
|
167
|
+
return;
|
|
168
|
+
const schema = value;
|
|
169
|
+
const rules = schema['x-astrale-rule'];
|
|
170
|
+
if (rules !== undefined) {
|
|
171
|
+
for (const rule of [rules].flat())
|
|
172
|
+
note(rule, pointer);
|
|
173
|
+
delete schema['x-astrale-rule'];
|
|
174
|
+
}
|
|
175
|
+
if (typeof schema.maxLength === 'number')
|
|
176
|
+
note('length-in-utf16-code-units', pointer);
|
|
177
|
+
for (const [key, entry] of Object.entries(schema)) {
|
|
178
|
+
visit(entry, `${pointer}/${escapePointerToken(key)}`);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
visit(document, '');
|
|
182
|
+
return positions;
|
|
183
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Value as JsonValue } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Read an unknown value as a Domain Schema document of its exact language contract: portable JSON
|
|
5
|
+
* (`DV_JSON_VALUE`), then the structure of its format and version. Throws `SchemaError`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function structureDomainSchema(input: unknown): DomainSchemaIR;
|
|
3
8
|
/** Select the exact Domain language contract before entering its structural grammar. */
|
|
4
9
|
export declare function routeDomainSchema(input: JsonValue): DomainSchemaIR;
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
|
|
1
2
|
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
2
3
|
import { DOMAIN_FORMAT, DOMAIN_VERSION } from '../model/format.js';
|
|
3
4
|
import { parseDomainSchemaStructure } from './domain.structure.js';
|
|
4
5
|
const decoders = new Map([
|
|
5
6
|
[DOMAIN_FORMAT, new Map([[DOMAIN_VERSION, parseDomainSchemaStructure]])],
|
|
6
7
|
]);
|
|
8
|
+
/**
|
|
9
|
+
* Read an unknown value as a Domain Schema document of its exact language contract: portable JSON
|
|
10
|
+
* (`DV_JSON_VALUE`), then the structure of its format and version. Throws `SchemaError`.
|
|
11
|
+
*/
|
|
12
|
+
export function structureDomainSchema(input) {
|
|
13
|
+
let admitted;
|
|
14
|
+
try {
|
|
15
|
+
admitted = acceptJsonValue(input);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof JsonBoundaryError) {
|
|
19
|
+
throw new SchemaError([diagnostic('DV_JSON_VALUE', error.pointer, error.message)]);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return routeDomainSchema(admitted);
|
|
24
|
+
}
|
|
7
25
|
/** Select the exact Domain language contract before entering its structural grammar. */
|
|
8
26
|
export function routeDomainSchema(input) {
|
|
9
27
|
if (!isJsonObject(input))
|