@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.
Files changed (103) hide show
  1. package/dist/v1/addressing/domain-dependency.d.ts +0 -6
  2. package/dist/v1/addressing/domain-dependency.js +1 -6
  3. package/dist/v1/addressing/key.js +2 -1
  4. package/dist/v1/addressing/patterns.d.ts +1 -0
  5. package/dist/v1/addressing/patterns.js +2 -1
  6. package/dist/v1/builder/class/authoring.d.ts +43 -0
  7. package/dist/v1/builder/class/property.d.ts +3 -9
  8. package/dist/v1/builder/class/property.js +1 -0
  9. package/dist/v1/builder/index.d.ts +1 -1
  10. package/dist/v1/builder/lowering/class.js +5 -1
  11. package/dist/v1/builder/lowering/policy.js +2 -0
  12. package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
  13. package/dist/v1/builder/lowering/schema/context.js +22 -0
  14. package/dist/v1/builder/policy/authoring.d.ts +84 -4
  15. package/dist/v1/builder/policy/authoring.js +44 -14
  16. package/dist/v1/builder/schema/projection/class.d.ts +116 -14
  17. package/dist/v1/bundle/codec.js +9 -9
  18. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  19. package/dist/v1/bundle/diagnostic.js +6 -0
  20. package/dist/v1/bundle/structure.js +2 -2
  21. package/dist/v1/compiled/admission/accept.js +3 -3
  22. package/dist/v1/compiled/admission/closure.js +2 -2
  23. package/dist/v1/compiled/admission/semantics/core.js +3 -7
  24. package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
  25. package/dist/v1/domain/validation/create.d.ts +4 -1
  26. package/dist/v1/domain/validation/create.js +21 -34
  27. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  28. package/dist/v1/language/class/model/class.d.ts +4 -4
  29. package/dist/v1/language/class/model/edge.d.ts +2 -2
  30. package/dist/v1/language/class/model/property.d.ts +16 -7
  31. package/dist/v1/language/core/facts.d.ts +17 -4
  32. package/dist/v1/language/core/facts.js +26 -5
  33. package/dist/v1/language/definition/metadata.d.ts +5 -0
  34. package/dist/v1/language/definition/metadata.js +7 -0
  35. package/dist/v1/language/limits.d.ts +64 -0
  36. package/dist/v1/language/limits.js +60 -0
  37. package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
  38. package/dist/v1/language/policy/analysis/budgets.js +30 -20
  39. package/dist/v1/language/policy/analysis/checks.js +3 -2
  40. package/dist/v1/language/policy/analysis/typing.js +4 -3
  41. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  42. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  43. package/dist/v1/language/validation/diagnostic.d.ts +1 -1
  44. package/dist/v1/language/validation/rule.d.ts +29 -0
  45. package/dist/v1/language/validation/rule.js +120 -0
  46. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  47. package/dist/v1/language/value/profile/rules.js +13 -37
  48. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  49. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  50. package/dist/v1/language/value/reference/registry.index.js +2 -1
  51. package/dist/v1/language/value/regexp/ast.js +6 -7
  52. package/dist/v1/language/value/regexp/compile.d.ts +5 -11
  53. package/dist/v1/language/value/regexp/compile.js +14 -12
  54. package/dist/v1/language/value/regexp/syntax.js +3 -2
  55. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  56. package/dist/v1/language/value/syntax/keywords.js +4 -20
  57. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  58. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  59. package/dist/v1/schema/admission/accept.js +3 -14
  60. package/dist/v1/schema/admission/callable.d.ts +21 -0
  61. package/dist/v1/schema/admission/callable.js +96 -0
  62. package/dist/v1/schema/analysis/class.js +33 -15
  63. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  64. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  65. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  66. package/dist/v1/schema/diagnostic.d.ts +17 -3
  67. package/dist/v1/schema/diagnostic.js +11 -0
  68. package/dist/v1/schema/index.d.ts +10 -1
  69. package/dist/v1/schema/index.js +4 -0
  70. package/dist/v1/schema/model/schema.d.ts +19 -1
  71. package/dist/v1/schema/normalization/domain.d.ts +2 -0
  72. package/dist/v1/schema/normalization/domain.js +2 -1
  73. package/dist/v1/schema/resolution/closure.js +2 -1
  74. package/dist/v1/schema/resolution/context.js +10 -8
  75. package/dist/v1/schema/resolution/domain.d.ts +5 -0
  76. package/dist/v1/schema/resolution/domain.js +1 -1
  77. package/dist/v1/schema/rule.d.ts +32 -0
  78. package/dist/v1/schema/rule.js +428 -0
  79. package/dist/v1/schema/validation/callable.d.ts +35 -2
  80. package/dist/v1/schema/validation/callable.js +51 -8
  81. package/dist/v1/schema/validation/class.js +36 -44
  82. package/dist/v1/schema/validation/core.js +19 -13
  83. package/dist/v1/schema/validation/domain.js +2 -2
  84. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  85. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  86. package/dist/v1/schema/validation/domain.route.d.ts +6 -1
  87. package/dist/v1/schema/validation/domain.route.js +18 -0
  88. package/dist/v1/schema/validation/domain.structure.d.ts +4020 -0
  89. package/dist/v1/schema/validation/domain.structure.js +359 -153
  90. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  91. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  92. package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
  93. package/dist/v1/schema/validation/policy/budgets.js +89 -16
  94. package/dist/v1/schema/validation/policy/references.js +6 -21
  95. package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
  96. package/dist/v1/schema/validation/policy/uses.js +28 -51
  97. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  98. package/dist/v1/schema/validation/value-api.js +10 -1
  99. package/dist/v1/schema/validation/value.d.ts +3 -0
  100. package/dist/v1/schema/validation/value.js +6 -12
  101. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  102. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  103. 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
- (declaration.kind !== 'node' || property.auto !== undefined || property.state !== undefined)) {
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 endpoints = declaration.orientation === 'directed'
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', '', `${formatDefinitionRef(owner)} has equal endpoint roles.`));
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
- function validateEndpoint(environment, owner, endpoint, count, side, diagnostics) {
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', '', `${formatDefinitionRef(owner)} ${side} accepts ${formatDefinitionRef(foreign)} of another Domain and cannot require ${count} Edges.`));
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 seen = new Set();
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', '', `${formatDefinitionRef(owner)} ${side} ref ${formatDefinitionRef(ref)} does not resolve to a Node Class.`));
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
- /** Edge inheritance is refinement, not a second interface composition mechanism. */
229
- function validateEdgeClassComposition(environment, diagnostics) {
230
- for (const resolved of environment.definitions.values()) {
231
- if (resolved.ref.kind !== 'class' || resolved.declaration.kind !== 'edge')
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
- const child = resolved.declaration;
234
- for (const parentRef of child.extends) {
235
- const parent = environment.resolveAny(parentRef);
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)}.`, 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
- // The owning Domain is a graph-projection identity, not a Definition declared by this DSL.
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 failure of validateJsonInstance(schema, properties, registry, pointer)) {
143
- diagnostics.push(diagnostic(code, failure.pointer, failure.message, failure.ref));
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 === 'member'
187
- ? { key: endpoint.key, member: { class: endpoint.classRef, pointer } }
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 { validateCallables } from './callable.js';
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
- ...validateCallables(schema),
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 { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
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))