@astrale-os/kernel-dsl 1.0.0-beta.12 → 1.0.0-beta.14

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 (152) hide show
  1. package/dist/v1/addressing/definition-ref.js +21 -37
  2. package/dist/v1/addressing/key.js +2 -1
  3. package/dist/v1/addressing/ordering.d.ts +2 -0
  4. package/dist/v1/addressing/ordering.js +4 -0
  5. package/dist/v1/addressing/patterns.d.ts +1 -1
  6. package/dist/v1/addressing/patterns.js +2 -6
  7. package/dist/v1/builder/class/authoring.js +5 -28
  8. package/dist/v1/builder/class/property.d.ts +3 -9
  9. package/dist/v1/builder/class/property.js +1 -0
  10. package/dist/v1/builder/lowering/class.js +5 -1
  11. package/dist/v1/builder/lowering/policy.js +1 -0
  12. package/dist/v1/builder/lowering/schema/context.js +2 -4
  13. package/dist/v1/builder/lowering/value/path.js +2 -4
  14. package/dist/v1/builder/lowering/value/zod.js +1 -3
  15. package/dist/v1/builder/policy/authoring.js +3 -2
  16. package/dist/v1/builder/schema/define.d.ts +0 -1
  17. package/dist/v1/builder/schema/diagnostic.d.ts +2 -1
  18. package/dist/v1/builder/schema/diagnostic.js +5 -10
  19. package/dist/v1/builder/schema/projection.d.ts +1 -1
  20. package/dist/v1/builder/value/full-match.d.ts +0 -1
  21. package/dist/v1/builder/value/full-match.js +0 -1
  22. package/dist/v1/bundle/codec.js +4 -6
  23. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  24. package/dist/v1/bundle/diagnostic.js +9 -7
  25. package/dist/v1/bundle/structure.js +4 -6
  26. package/dist/v1/compiled/admission/diagnostic.d.ts +1 -1
  27. package/dist/v1/compiled/admission/diagnostic.js +1 -3
  28. package/dist/v1/compiled/admission/revision.d.ts +9 -1
  29. package/dist/v1/compiled/admission/revision.js +6 -3
  30. package/dist/v1/compiled/admission/semantics/class.d.ts +4 -1
  31. package/dist/v1/compiled/admission/semantics/class.js +4 -8
  32. package/dist/v1/compiled/admission/semantics/core.d.ts +1 -24
  33. package/dist/v1/compiled/admission/semantics/core.js +54 -34
  34. package/dist/v1/compiled/admission/semantics/edge.d.ts +2 -4
  35. package/dist/v1/compiled/admission/semantics/edge.js +6 -65
  36. package/dist/v1/compiled/admission/semantics/policy/callable.js +0 -2
  37. package/dist/v1/compiled/admission/semantics/policy/contracts.d.ts +5 -20
  38. package/dist/v1/compiled/admission/semantics/policy/contracts.js +8 -21
  39. package/dist/v1/compiled/admission/semantics/policy/parity.js +13 -22
  40. package/dist/v1/compiled/admission/semantics/policy/pattern.d.ts +4 -1
  41. package/dist/v1/compiled/admission/semantics/policy/pattern.js +4 -16
  42. package/dist/v1/compiled/admission/semantics/policy/typing.d.ts +2 -24
  43. package/dist/v1/compiled/admission/semantics/policy/typing.js +19 -201
  44. package/dist/v1/compiled/compiler/compile.js +6 -8
  45. package/dist/v1/compiled/diagnostic.js +2 -3
  46. package/dist/v1/compiled/loader/load.d.ts +3 -2
  47. package/dist/v1/compiled/loader/load.js +10 -44
  48. package/dist/v1/domain/create.js +3 -5
  49. package/dist/v1/domain/namespace/member-collection.js +4 -6
  50. package/dist/v1/domain/validation/create.d.ts +4 -2
  51. package/dist/v1/domain/validation/create.js +21 -69
  52. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  53. package/dist/v1/language/class/inheritance.d.ts +18 -0
  54. package/dist/v1/language/class/inheritance.js +73 -0
  55. package/dist/v1/language/class/model/class.d.ts +4 -4
  56. package/dist/v1/language/class/model/edge.d.ts +2 -2
  57. package/dist/v1/language/class/model/property.d.ts +16 -7
  58. package/dist/v1/language/class/property-values.js +2 -4
  59. package/dist/v1/language/core/facts.d.ts +72 -0
  60. package/dist/v1/language/core/facts.js +95 -0
  61. package/dist/v1/language/definition/metadata.d.ts +5 -0
  62. package/dist/v1/language/definition/metadata.js +7 -0
  63. package/dist/v1/language/policy/analysis/branches.d.ts +8 -0
  64. package/dist/v1/language/policy/analysis/branches.js +2 -1
  65. package/dist/v1/language/policy/analysis/budgets.d.ts +13 -1
  66. package/dist/v1/language/policy/analysis/budgets.js +104 -0
  67. package/dist/v1/language/policy/analysis/typing.d.ts +48 -0
  68. package/dist/v1/language/policy/analysis/typing.js +338 -0
  69. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  70. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  71. package/dist/v1/language/validation/diagnostic.d.ts +15 -1
  72. package/dist/v1/language/validation/diagnostic.js +21 -0
  73. package/dist/v1/language/validation/rule.d.ts +26 -0
  74. package/dist/v1/language/validation/rule.js +116 -0
  75. package/dist/v1/language/value/compile/fields.js +4 -8
  76. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  77. package/dist/v1/language/value/profile/rules.js +17 -43
  78. package/dist/v1/language/value/profile/validate.js +1 -10
  79. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  80. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  81. package/dist/v1/language/value/reference/recursion.js +2 -22
  82. package/dist/v1/language/value/reference/registry.index.js +2 -1
  83. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  84. package/dist/v1/language/value/syntax/keywords.js +4 -20
  85. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  86. package/dist/v1/language/value/syntax/pointer.d.ts +2 -0
  87. package/dist/v1/language/value/syntax/pointer.js +5 -2
  88. package/dist/v1/language/value/syntax/walk.d.ts +2 -0
  89. package/dist/v1/language/value/syntax/walk.js +19 -20
  90. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  91. package/dist/v1/schema/analysis/class.js +33 -15
  92. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  93. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  94. package/dist/v1/schema/compatibility/diff.js +2 -4
  95. package/dist/v1/schema/compatibility/meaning/footprint.js +16 -50
  96. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  97. package/dist/v1/schema/compatibility/structure/requirements.js +26 -58
  98. package/dist/v1/schema/compatibility/walk.d.ts +17 -0
  99. package/dist/v1/schema/compatibility/walk.js +41 -0
  100. package/dist/v1/schema/diagnostic.d.ts +17 -3
  101. package/dist/v1/schema/diagnostic.js +14 -11
  102. package/dist/v1/schema/index.d.ts +3 -1
  103. package/dist/v1/schema/index.js +1 -0
  104. package/dist/v1/schema/model/schema.d.ts +19 -1
  105. package/dist/v1/schema/normalization/domain.js +1 -0
  106. package/dist/v1/schema/resolution/closure.js +2 -1
  107. package/dist/v1/schema/resolution/context.js +6 -6
  108. package/dist/v1/schema/rule.d.ts +29 -0
  109. package/dist/v1/schema/rule.js +415 -0
  110. package/dist/v1/schema/validation/callable.d.ts +4 -1
  111. package/dist/v1/schema/validation/callable.js +13 -6
  112. package/dist/v1/schema/validation/class.js +46 -96
  113. package/dist/v1/schema/validation/core.js +68 -202
  114. package/dist/v1/schema/validation/domain.js +1 -10
  115. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  116. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  117. package/dist/v1/schema/validation/domain.structure.d.ts +4018 -0
  118. package/dist/v1/schema/validation/domain.structure.js +343 -155
  119. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  120. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  121. package/dist/v1/schema/validation/policy/budgets.js +5 -112
  122. package/dist/v1/schema/validation/policy/context.d.ts +5 -3
  123. package/dist/v1/schema/validation/policy/context.js +5 -3
  124. package/dist/v1/schema/validation/policy/objects.d.ts +1 -1
  125. package/dist/v1/schema/validation/policy/objects.js +6 -14
  126. package/dist/v1/schema/validation/policy/references.js +10 -27
  127. package/dist/v1/schema/validation/policy/typing.d.ts +3 -18
  128. package/dist/v1/schema/validation/policy/typing.js +22 -356
  129. package/dist/v1/schema/validation/policy/uses.d.ts +1 -1
  130. package/dist/v1/schema/validation/policy/uses.js +9 -25
  131. package/dist/v1/schema/validation/policy/validate.js +3 -2
  132. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  133. package/dist/v1/schema/validation/value-api.js +12 -3
  134. package/dist/v1/schema/validation/value.js +8 -10
  135. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  136. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  137. package/dist/v1/schema/validation/view.js +4 -8
  138. package/package.json +3 -1
  139. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +0 -5
  140. package/dist/v1/compiled/admission/semantics/cardinality.js +0 -128
  141. package/dist/v1/compiled/admission/semantics/policy/equality.d.ts +0 -8
  142. package/dist/v1/compiled/admission/semantics/policy/equality.js +0 -27
  143. package/dist/v1/compiled/admission/semantics/policy/measure.d.ts +0 -9
  144. package/dist/v1/compiled/admission/semantics/policy/measure.js +0 -81
  145. package/dist/v1/compiled/admission/semantics/policy/variants.d.ts +0 -10
  146. package/dist/v1/compiled/admission/semantics/policy/variants.js +0 -65
  147. package/dist/v1/language/class/index.d.ts +0 -8
  148. package/dist/v1/language/class/index.js +0 -1
  149. package/dist/v1/language/core/index.d.ts +0 -2
  150. package/dist/v1/language/core/index.js +0 -1
  151. package/dist/v1/language/value/index.d.ts +0 -11
  152. package/dist/v1/language/value/index.js +0 -8
@@ -1,3 +1,5 @@
1
+ import { compareCodeUnits } from '../../../addressing/ordering.js';
2
+ import { escapePointerToken } from '../syntax/pointer.js';
1
3
  /** Exact field constraints available for independent scalar route validation. */
2
4
  export function inputFieldNodes(program) {
3
5
  const fields = new Map();
@@ -34,7 +36,7 @@ export function inputFieldNodes(program) {
34
36
  Array.isArray(schema.properties))
35
37
  return;
36
38
  for (const name of Object.keys(schema.properties)) {
37
- const child = node.children[`properties/${escapePointer(name)}`];
39
+ const child = node.children[`properties/${escapePointerToken(name)}`];
38
40
  if (child === undefined)
39
41
  throw new TypeError(`Compiled input field ${name} is absent.`);
40
42
  const constraints = fields.get(name) ?? new Set();
@@ -46,15 +48,9 @@ export function inputFieldNodes(program) {
46
48
  if (!closed || unsupported)
47
49
  return undefined;
48
50
  return Object.freeze(Object.fromEntries([...fields]
49
- .sort(([left], [right]) => compare(left, right))
51
+ .sort(([left], [right]) => compareCodeUnits(left, right))
50
52
  .map(([name, nodes]) => [
51
53
  name,
52
54
  Object.freeze([...nodes].sort((left, right) => left - right)),
53
55
  ])));
54
56
  }
55
- function escapePointer(value) {
56
- return value.replaceAll('~', '~0').replaceAll('/', '~1');
57
- }
58
- function compare(left, right) {
59
- return left < right ? -1 : left > right ? 1 : 0;
60
- }
@@ -4,7 +4,7 @@
4
4
  "use strict";
5
5
  export const validate = validate20;
6
6
  export default validate20;
7
- const schema31 = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.astrale.ai/dsl/v1/value-schema", "$vocabulary": {}, "allOf": [{ "$ref": "https://json-schema.org/draft/2020-12/schema" }], "properties": { "fullMatch": { "type": "string" }, "x-astrale-path": { "type": "object" } } };
7
+ const schema31 = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.astrale.ai/dsl/v1/value-schema", "$dynamicAnchor": "meta", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true, "https://json-schema.org/draft/2020-12/vocab/applicator": true, "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, "https://json-schema.org/draft/2020-12/vocab/validation": true, "https://json-schema.org/draft/2020-12/vocab/meta-data": true, "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, "https://json-schema.org/draft/2020-12/vocab/content": true, "https://schemas.astrale.ai/dsl/v1/value-schema/vocabulary": true }, "title": "Astrale V1 value schema", "$comment": "The meta-schema of the V1 value-schema dialect: Draft 2020-12 and the required Astrale vocabulary (fullMatch, x-astrale-path, x-astrale-value-kind). Its $dynamicAnchor extends Draft 2020-12 at every subschema, not only the root. The rules of the profile beyond it are published as schema.values.RULES.", "allOf": [{ "$ref": "https://json-schema.org/draft/2020-12/schema" }], "properties": { "fullMatch": { "type": "string" }, "x-astrale-path": { "type": "object" } } };
8
8
  const schema0 = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true, "https://json-schema.org/draft/2020-12/vocab/applicator": true, "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, "https://json-schema.org/draft/2020-12/vocab/validation": true, "https://json-schema.org/draft/2020-12/vocab/meta-data": true, "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, "https://json-schema.org/draft/2020-12/vocab/content": true }, "$dynamicAnchor": "meta", "title": "Core and Validation specifications meta-schema", "allOf": [{ "$ref": "meta/core" }, { "$ref": "meta/applicator" }, { "$ref": "meta/unevaluated" }, { "$ref": "meta/validation" }, { "$ref": "meta/meta-data" }, { "$ref": "meta/format-annotation" }, { "$ref": "meta/content" }], "type": ["object", "boolean"], "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", "properties": { "definitions": { "$comment": "\"definitions\" has been replaced by \"$defs\".", "type": "object", "additionalProperties": { "$dynamicRef": "#meta" }, "deprecated": true, "default": {} }, "dependencies": { "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", "type": "object", "additionalProperties": { "anyOf": [{ "$dynamicRef": "#meta" }, { "$ref": "meta/validation#/$defs/stringArray" }] }, "deprecated": true, "default": {} }, "$recursiveAnchor": { "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", "$ref": "meta/core#/$defs/anchorString", "deprecated": true }, "$recursiveRef": { "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", "$ref": "meta/core#/$defs/uriReferenceString", "deprecated": true } } };
9
9
  const schema23 = { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "default": [] };
10
10
  const schema5 = { "type": "string", "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" };
@@ -2042,6 +2042,9 @@ function validate20(data, { instancePath = "", parentData, parentDataProperty, r
2042
2042
  if (evaluated0.dynamicItems) {
2043
2043
  evaluated0.items = undefined;
2044
2044
  }
2045
+ if (!dynamicAnchors.meta) {
2046
+ dynamicAnchors.meta = validate20;
2047
+ }
2045
2048
  if (!(validate0(data, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }))) {
2046
2049
  vErrors = vErrors === null ? validate0.errors : vErrors.concat(validate0.errors);
2047
2050
  errors = vErrors.length;
@@ -4,8 +4,11 @@ import { blob, BLOB_VALUE_KIND } from '../model/blob.js';
4
4
  import { isValueObject as isJsonObject } from '../model/object.js';
5
5
  import { VALUE_SCHEMA_DIALECT } from '../model/schema.js';
6
6
  import { refuseIRegexp, stateBudgetMessage } from '../regexp/syntax.js';
7
+ import { escapePointerToken } from '../syntax/pointer.js';
7
8
  import { walkSchema } from '../syntax/walk.js';
8
- import { FORBIDDEN_DYNAMIC_KEYWORDS, STANDARD_SCHEMA_KEYWORDS } from './vocabulary.js';
9
+ import { FORBIDDEN_KEYWORDS, VALUE_KEYWORDS } from './vocabulary.js';
10
+ const KEYWORDS = new Set(VALUE_KEYWORDS);
11
+ const FORBIDDEN = new Set(FORBIDDEN_KEYWORDS);
9
12
  export function validateSchemaProfile(schema, context, pointer) {
10
13
  const diagnostics = [];
11
14
  walkSchema(schema, (location) => {
@@ -13,11 +16,11 @@ export function validateSchemaProfile(schema, context, pointer) {
13
16
  return;
14
17
  const currentPointer = `${pointer}${location.pointer}`;
15
18
  for (const keyword of Object.keys(location.schema)) {
16
- if (!STANDARD_SCHEMA_KEYWORDS.has(keyword)) {
17
- diagnostics.push(diagnostic('VS_KEYWORD_CLOSED', `${currentPointer}/${escapeToken(keyword)}`, `Keyword ${keyword} is outside the closed V1 Draft 2020-12 vocabulary.`));
19
+ if (!KEYWORDS.has(keyword)) {
20
+ diagnostics.push(diagnostic('VS_KEYWORD_CLOSED', `${currentPointer}/${escapePointerToken(keyword)}`, `Keyword ${keyword} is outside the closed V1 Draft 2020-12 vocabulary.`));
18
21
  }
19
- if (FORBIDDEN_DYNAMIC_KEYWORDS.has(keyword)) {
20
- diagnostics.push(diagnostic('VS_DYNAMIC_FORBIDDEN', `${currentPointer}/${escapeToken(keyword)}`, `Keyword ${keyword} is forbidden by V1.`));
22
+ if (FORBIDDEN.has(keyword)) {
23
+ diagnostics.push(diagnostic('VS_DYNAMIC_FORBIDDEN', `${currentPointer}/${escapePointerToken(keyword)}`, `Keyword ${keyword} is forbidden by V1.`));
21
24
  }
22
25
  }
23
26
  if (Object.hasOwn(location.schema, '$schema')) {
@@ -28,7 +31,11 @@ export function validateSchemaProfile(schema, context, pointer) {
28
31
  if (Object.hasOwn(location.schema, '$id')) {
29
32
  diagnostics.push(diagnostic('VS_RESOURCE_ID', `${currentPointer}/$id`, '$id is forbidden in an embedded V1 value schema.'));
30
33
  }
31
- validateSemanticSets(location.schema, currentPointer, diagnostics);
34
+ // The dialect refuses a repeated type, required or dependentRequired entry; it lets an enum
35
+ // repeat a value.
36
+ if (Array.isArray(location.schema.enum)) {
37
+ rejectCanonicalDuplicates(location.schema.enum, `${currentPointer}/enum`, diagnostics);
38
+ }
32
39
  validateValueKind(location.schema, context, location.depth, currentPointer, diagnostics);
33
40
  validateRegexes(location.schema, currentPointer, diagnostics);
34
41
  });
@@ -73,28 +80,8 @@ function isCanonicalBlobSchema(input) {
73
80
  function isString(input) {
74
81
  return typeof input === 'string';
75
82
  }
76
- function validateSemanticSets(schema, pointer, diagnostics) {
77
- if (Array.isArray(schema.type)) {
78
- rejectPrimitiveDuplicates(schema.type, `${pointer}/type`, 'type', diagnostics);
79
- }
80
- if (Array.isArray(schema.required)) {
81
- rejectPrimitiveDuplicates(schema.required, `${pointer}/required`, 'required', diagnostics);
82
- }
83
- if (Array.isArray(schema.enum)) {
84
- rejectCanonicalDuplicates(schema.enum, `${pointer}/enum`, 'enum', diagnostics);
85
- }
86
- if (isJsonObject(schema.dependentRequired)) {
87
- for (const [name, dependencies] of Object.entries(schema.dependentRequired)) {
88
- if (Array.isArray(dependencies)) {
89
- rejectPrimitiveDuplicates(dependencies, `${pointer}/dependentRequired/${escapeToken(name)}`, 'dependentRequired', diagnostics);
90
- }
91
- }
92
- }
93
- }
83
+ /** The dialect refuses a fullMatch that is not a string, at every location. */
94
84
  function validateRegexes(schema, pointer, diagnostics) {
95
- if (Object.hasOwn(schema, 'fullMatch') && typeof schema.fullMatch !== 'string') {
96
- diagnostics.push(diagnostic('VS_META_SCHEMA', `${pointer}/fullMatch`, 'fullMatch must be an I-Regexp source string.'));
97
- }
98
85
  if (typeof schema.fullMatch === 'string') {
99
86
  portableRegex(schema.fullMatch, `${pointer}/fullMatch`, 'fullMatch', 'fullMatch must satisfy the complete RFC 9485 I-Regexp syntax.', diagnostics);
100
87
  }
@@ -103,7 +90,7 @@ function validateRegexes(schema, pointer, diagnostics) {
103
90
  }
104
91
  if (isJsonObject(schema.patternProperties)) {
105
92
  for (const pattern of Object.keys(schema.patternProperties)) {
106
- portableRegex(pattern, `${pointer}/patternProperties/${escapeToken(pattern)}`, 'A patternProperties key', 'patternProperties keys must satisfy RFC 9485 I-Regexp syntax.', diagnostics);
93
+ portableRegex(pattern, `${pointer}/patternProperties/${escapePointerToken(pattern)}`, 'A patternProperties key', 'patternProperties keys must satisfy RFC 9485 I-Regexp syntax.', diagnostics);
107
94
  }
108
95
  }
109
96
  }
@@ -113,17 +100,7 @@ function portableRegex(source, pointer, subject, syntax, diagnostics) {
113
100
  return;
114
101
  diagnostics.push(diagnostic('VS_REGEX_PORTABLE', pointer, refusal === 'syntax' ? syntax : stateBudgetMessage(subject)));
115
102
  }
116
- function rejectPrimitiveDuplicates(values, pointer, name, diagnostics) {
117
- const seen = new Set();
118
- for (const value of values) {
119
- if (seen.has(value)) {
120
- diagnostics.push(diagnostic('VS_META_SCHEMA', pointer, `${name} is a duplicate-free semantic set.`));
121
- return;
122
- }
123
- seen.add(value);
124
- }
125
- }
126
- function rejectCanonicalDuplicates(values, pointer, name, diagnostics) {
103
+ function rejectCanonicalDuplicates(values, pointer, diagnostics) {
127
104
  const seen = new Set();
128
105
  for (const value of values) {
129
106
  let key;
@@ -134,12 +111,9 @@ function rejectCanonicalDuplicates(values, pointer, name, diagnostics) {
134
111
  continue;
135
112
  }
136
113
  if (seen.has(key)) {
137
- diagnostics.push(diagnostic('VS_META_SCHEMA', pointer, `${name} rejects canonically equal duplicates.`));
114
+ diagnostics.push(diagnostic('VS_META_SCHEMA', pointer, 'enum rejects canonically equal duplicates.'));
138
115
  return;
139
116
  }
140
117
  seen.add(key);
141
118
  }
142
119
  }
143
- function escapeToken(value) {
144
- return value.replaceAll('~', '~0').replaceAll('/', '~1');
145
- }
@@ -1,4 +1,4 @@
1
- import { compareUnicode } from '../../../addressing/ordering.js';
1
+ import { orderAndDeduplicate } from '../../validation/diagnostic.js';
2
2
  import { collectResourceClosure } from '../reference/closure.js';
3
3
  import { validateProductiveRecursion } from '../reference/recursion.js';
4
4
  import { hasNodeDefinition, indexRegistry } from '../reference/registry.index.js';
@@ -28,12 +28,3 @@ export function validateValueSchema(schema, context, registry, pointer = '') {
28
28
  diagnostics.push(...validateProductiveRecursion(closure.resources, closure.resolutions));
29
29
  return orderAndDeduplicate(diagnostics);
30
30
  }
31
- function orderAndDeduplicate(diagnostics) {
32
- const unique = new Map();
33
- for (const value of diagnostics) {
34
- unique.set(`${value.pointer}\u0000${value.code}\u0000${value.ref ?? ''}\u0000${value.message}`, value);
35
- }
36
- return [...unique.values()].sort((left, right) => compareUnicode(left.pointer, right.pointer) ||
37
- compareUnicode(left.code, right.code) ||
38
- compareUnicode(left.message, right.message));
39
- }
@@ -1,2 +1,35 @@
1
- export declare const STANDARD_SCHEMA_KEYWORDS: Set<string>;
2
- export declare const FORBIDDEN_DYNAMIC_KEYWORDS: Set<string>;
1
+ declare const keywords: readonly ['$anchor', '$comment', '$defs', '$dynamicAnchor', '$dynamicRef', '$id', '$ref', '$schema', '$vocabulary', 'additionalProperties', 'allOf', 'anyOf', 'const', 'contains', 'contentEncoding', 'contentMediaType', 'contentSchema', 'default', 'dependentRequired', 'dependentSchemas', 'deprecated', 'description', 'else', 'enum', 'examples', 'exclusiveMaximum', 'exclusiveMinimum', 'format', 'fullMatch', 'if', 'items', 'maxContains', 'maxItems', 'maxLength', 'maxProperties', 'maximum', 'minContains', 'minItems', 'minLength', 'minProperties', 'minimum', 'multipleOf', 'not', 'oneOf', 'pattern', 'patternProperties', 'prefixItems', 'properties', 'propertyNames', 'readOnly', 'required', 'then', 'title', 'type', 'unevaluatedItems', 'unevaluatedProperties', 'uniqueItems', 'writeOnly', 'x-astrale-path', 'x-astrale-value-kind'];
2
+ export type ValueKeyword = (typeof keywords)[number];
3
+ /**
4
+ * The closed V1 vocabulary: every keyword a value schema may name, the Astrale profile's included
5
+ * (`fullMatch`, `x-astrale-path`, `x-astrale-value-kind`). Any other keyword is refused
6
+ * (`VS_KEYWORD_CLOSED`). Other rules restrict some of these: the forbidden keywords never appear,
7
+ * `$id` never, `$schema` only at the root. Frozen and Unicode-ordered.
8
+ */
9
+ export declare const VALUE_KEYWORDS: readonly ValueKeyword[];
10
+ /** The keywords of the vocabulary refused wherever they appear (`VS_DYNAMIC_FORBIDDEN`). */
11
+ export declare const FORBIDDEN_KEYWORDS: readonly ValueKeyword[];
12
+ /**
13
+ * The keywords whose value holds subschemas, by its shape: one schema, an array of schemas, or an
14
+ * object of named schemas. A value schema's locations are its root and the subschemas these hold.
15
+ */
16
+ export declare const SUBSCHEMA_KEYWORDS: {
17
+ readonly single: readonly ValueKeyword[];
18
+ readonly array: readonly ValueKeyword[];
19
+ readonly map: readonly ValueKeyword[];
20
+ };
21
+ /** A value kind a schema declares with `x-astrale-value-kind`, because no JSON type gives it. */
22
+ export interface DeclaredValueKind {
23
+ /** The JSON type of its values, which the schema states beside the declaration. */
24
+ readonly type: 'string' | 'object';
25
+ /**
26
+ * Where a schema may declare it: at any location of any value schema, or only as the whole
27
+ * schema of a Node Property that is neither automatic nor a State Property.
28
+ */
29
+ readonly placement: 'any' | 'node-property';
30
+ /** The kind, in one plain sentence. */
31
+ readonly statement: string;
32
+ }
33
+ /** The value kinds `x-astrale-value-kind` declares; it admits no other value. */
34
+ export declare const DECLARED_VALUE_KINDS: Readonly<Record<'instant' | 'blob', DeclaredValueKind>>;
35
+ export {};
@@ -1,4 +1,4 @@
1
- export const STANDARD_SCHEMA_KEYWORDS = new Set([
1
+ const keywords = [
2
2
  '$anchor',
3
3
  '$comment',
4
4
  '$defs',
@@ -31,15 +31,15 @@ export const STANDARD_SCHEMA_KEYWORDS = new Set([
31
31
  'if',
32
32
  'items',
33
33
  'maxContains',
34
- 'maximum',
35
34
  'maxItems',
36
35
  'maxLength',
37
36
  'maxProperties',
37
+ 'maximum',
38
38
  'minContains',
39
- 'minimum',
40
39
  'minItems',
41
40
  'minLength',
42
41
  'minProperties',
42
+ 'minimum',
43
43
  'multipleOf',
44
44
  'not',
45
45
  'oneOf',
@@ -59,10 +59,57 @@ export const STANDARD_SCHEMA_KEYWORDS = new Set([
59
59
  'writeOnly',
60
60
  'x-astrale-path',
61
61
  'x-astrale-value-kind',
62
- ]);
63
- export const FORBIDDEN_DYNAMIC_KEYWORDS = new Set([
62
+ ];
63
+ /**
64
+ * The closed V1 vocabulary: every keyword a value schema may name, the Astrale profile's included
65
+ * (`fullMatch`, `x-astrale-path`, `x-astrale-value-kind`). Any other keyword is refused
66
+ * (`VS_KEYWORD_CLOSED`). Other rules restrict some of these: the forbidden keywords never appear,
67
+ * `$id` never, `$schema` only at the root. Frozen and Unicode-ordered.
68
+ */
69
+ export const VALUE_KEYWORDS = Object.freeze(keywords);
70
+ /** The keywords of the vocabulary refused wherever they appear (`VS_DYNAMIC_FORBIDDEN`). */
71
+ export const FORBIDDEN_KEYWORDS = Object.freeze([
64
72
  '$anchor',
65
73
  '$dynamicAnchor',
66
74
  '$dynamicRef',
67
75
  '$vocabulary',
68
76
  ]);
77
+ /**
78
+ * The keywords whose value holds subschemas, by its shape: one schema, an array of schemas, or an
79
+ * object of named schemas. A value schema's locations are its root and the subschemas these hold.
80
+ */
81
+ export const SUBSCHEMA_KEYWORDS = Object.freeze({
82
+ single: Object.freeze([
83
+ 'additionalProperties',
84
+ 'contains',
85
+ 'contentSchema',
86
+ 'else',
87
+ 'if',
88
+ 'items',
89
+ 'not',
90
+ 'propertyNames',
91
+ 'then',
92
+ 'unevaluatedItems',
93
+ 'unevaluatedProperties',
94
+ ]),
95
+ array: Object.freeze(['allOf', 'anyOf', 'oneOf', 'prefixItems']),
96
+ map: Object.freeze([
97
+ '$defs',
98
+ 'dependentSchemas',
99
+ 'patternProperties',
100
+ 'properties',
101
+ ]),
102
+ });
103
+ /** The value kinds `x-astrale-value-kind` declares; it admits no other value. */
104
+ export const DECLARED_VALUE_KINDS = Object.freeze({
105
+ instant: Object.freeze({
106
+ type: 'string',
107
+ placement: 'any',
108
+ statement: 'A UTC instant written YYYY-MM-DDTHH:mm:ss.sssZ, in the years 0000 to 9999, whose text order is its chronological order.',
109
+ }),
110
+ blob: Object.freeze({
111
+ type: 'object',
112
+ placement: 'node-property',
113
+ statement: 'The Ref of stored contents, its digest, media type and size: the schema is exactly a values.blob output, and the Property is written with an upload receipt.',
114
+ }),
115
+ });
@@ -1,8 +1,7 @@
1
1
  import { diagnostic } from '../../validation/diagnostic.js';
2
2
  import { isValueObject as isJsonObject } from '../model/object.js';
3
- import { SCHEMA_ARRAY_KEYWORDS, SCHEMA_MAP_KEYWORDS, SCHEMA_SINGLE_KEYWORDS, } from '../syntax/keywords.js';
4
3
  import { appendPointer } from '../syntax/pointer.js';
5
- import { isJsonSchema, walkSchema } from '../syntax/walk.js';
4
+ import { forEachSubschema, walkSchema } from '../syntax/walk.js';
6
5
  import { locationKey } from './closure.js';
7
6
  export const CONSUMING_SCHEMA_KEYWORDS = new Set([
8
7
  'additionalProperties',
@@ -43,26 +42,7 @@ export function validateProductiveRecursion(resources, resolutions) {
43
42
  for (const [keyword, value] of Object.entries(location.schema)) {
44
43
  if (!NON_CONSUMING_SCHEMA_KEYWORDS.has(keyword))
45
44
  continue;
46
- const keywordPointer = appendPointer(location.pointer, keyword);
47
- if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(value)) {
48
- outgoing.add(locationKey(resource.key, keywordPointer));
49
- continue;
50
- }
51
- if (SCHEMA_ARRAY_KEYWORDS.has(keyword) && Array.isArray(value)) {
52
- value.forEach((entry, index) => {
53
- if (isJsonSchema(entry)) {
54
- outgoing.add(locationKey(resource.key, appendPointer(keywordPointer, index)));
55
- }
56
- });
57
- continue;
58
- }
59
- if (SCHEMA_MAP_KEYWORDS.has(keyword) && isJsonObject(value)) {
60
- for (const [name, entry] of Object.entries(value)) {
61
- if (isJsonSchema(entry)) {
62
- outgoing.add(locationKey(resource.key, appendPointer(keywordPointer, name)));
63
- }
64
- }
65
- }
45
+ forEachSubschema(keyword, value, appendPointer(location.pointer, keyword), (_, at) => outgoing.add(locationKey(resource.key, at)));
66
46
  }
67
47
  });
68
48
  }
@@ -31,8 +31,9 @@ export function indexRegistry(registry, pointer) {
31
31
  dependenciesByOwner.set(domain.origin, pins);
32
32
  for (const dependency of domain.dependencies) {
33
33
  const target = domains.get(dependency.origin);
34
+ // The schema at `pointer` cannot be judged without the closure it may reference.
34
35
  if (target === undefined || target.revision !== dependency.revision) {
35
- diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', `${pointer}/registry/${domain.origin}/dependencies`, `Registry closure is missing exact ${dependency.origin}@${dependency.revision} required by ${domain.origin}.`));
36
+ diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', pointer, `Registry closure is missing exact ${dependency.origin}@${dependency.revision} required by ${domain.origin}.`));
36
37
  }
37
38
  }
38
39
  }
@@ -1,3 +1,3 @@
1
- export declare const SCHEMA_ARRAY_KEYWORDS: Set<string>;
2
- export declare const SCHEMA_MAP_KEYWORDS: Set<string>;
3
- export declare const SCHEMA_SINGLE_KEYWORDS: Set<string>;
1
+ export declare const SCHEMA_ARRAY_KEYWORDS: ReadonlySet<string>;
2
+ export declare const SCHEMA_MAP_KEYWORDS: ReadonlySet<string>;
3
+ export declare const SCHEMA_SINGLE_KEYWORDS: ReadonlySet<string>;
@@ -1,20 +1,4 @@
1
- export const SCHEMA_ARRAY_KEYWORDS = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
2
- export const SCHEMA_MAP_KEYWORDS = new Set([
3
- '$defs',
4
- 'dependentSchemas',
5
- 'patternProperties',
6
- 'properties',
7
- ]);
8
- export const SCHEMA_SINGLE_KEYWORDS = new Set([
9
- 'additionalProperties',
10
- 'contains',
11
- 'contentSchema',
12
- 'else',
13
- 'if',
14
- 'items',
15
- 'not',
16
- 'propertyNames',
17
- 'then',
18
- 'unevaluatedItems',
19
- 'unevaluatedProperties',
20
- ]);
1
+ import { SUBSCHEMA_KEYWORDS } from '../profile/vocabulary.js';
2
+ export const SCHEMA_ARRAY_KEYWORDS = new Set(SUBSCHEMA_KEYWORDS.array);
3
+ export const SCHEMA_MAP_KEYWORDS = new Set(SUBSCHEMA_KEYWORDS.map);
4
+ export const SCHEMA_SINGLE_KEYWORDS = new Set(SUBSCHEMA_KEYWORDS.single);
@@ -29,11 +29,10 @@ export function validateNodePathExtension(schema, context, pointer, definitionEx
29
29
  location.schema.$ref !== GRAPH_NODE_PATH_SCHEMA_ID) {
30
30
  diagnostics.push(diagnostic('VS_PATH_EXTENSION', `${pointer}${location.pointer}`, `A Path field schema contains exactly $ref ${GRAPH_NODE_PATH_SCHEMA_ID} and x-astrale-path.`, GRAPH_NODE_PATH_SCHEMA_ID));
31
31
  }
32
+ // The dialect refuses an annotation that is not an object, at every location.
32
33
  const annotation = location.schema['x-astrale-path'];
33
- if (!isJsonObject(annotation)) {
34
- diagnostics.push(diagnostic('VS_PATH_EXTENSION', extensionPointer, 'x-astrale-path must be a closed annotation object.'));
34
+ if (!isJsonObject(annotation))
35
35
  return;
36
- }
37
36
  if (!hasExactKeys(annotation, ['target', 'cardinality', 'accepts'])) {
38
37
  diagnostics.push(diagnostic('VS_PATH_EXTENSION', extensionPointer, 'x-astrale-path contains exactly target, cardinality, and accepts.'));
39
38
  }
@@ -1,3 +1,5 @@
1
+ /** Escape one JSON Pointer reference token: `~` as `~0`, then `/` as `~1`. */
2
+ export declare function escapePointerToken(token: string): string;
1
3
  export declare function appendPointer(pointer: string, token: string | number): string;
2
4
  export declare function parseJsonPointerFragment(fragment: string): readonly string[] | undefined;
3
5
  export declare function tokensToPointer(tokens: readonly string[]): string;
@@ -1,6 +1,9 @@
1
+ /** Escape one JSON Pointer reference token: `~` as `~0`, then `/` as `~1`. */
2
+ export function escapePointerToken(token) {
3
+ return token.replaceAll('~', '~0').replaceAll('/', '~1');
4
+ }
1
5
  export function appendPointer(pointer, token) {
2
- const encoded = String(token).replaceAll('~', '~0').replaceAll('/', '~1');
3
- return `${pointer}/${encoded}`;
6
+ return `${pointer}/${escapePointerToken(String(token))}`;
4
7
  }
5
8
  export function parseJsonPointerFragment(fragment) {
6
9
  if (!fragment.startsWith('#'))
@@ -6,5 +6,7 @@ export interface SchemaLocation {
6
6
  readonly parentKeyword?: string;
7
7
  }
8
8
  export declare function walkSchema(schema: JsonSchema, visit: (location: SchemaLocation) => void, pointer?: string): void;
9
+ /** Visit each subschema `keyword` holds directly, with its pointer under `pointer`. */
10
+ export declare function forEachSubschema(keyword: string, value: unknown, pointer: string, visit: (schema: JsonSchema, pointer: string) => void): void;
9
11
  export declare function collectSchemaPointers(schema: JsonSchema): ReadonlySet<string>;
10
12
  export declare function isJsonSchema(value: unknown): value is JsonSchema;
@@ -14,26 +14,7 @@ export function walkSchema(schema, visit, pointer = '') {
14
14
  if (!isJsonObject(current))
15
15
  return;
16
16
  for (const [keyword, value] of Object.entries(current)) {
17
- const keywordPointer = appendPointer(currentPointer, keyword);
18
- if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(value)) {
19
- walk(value, keywordPointer, depth + 1, keyword);
20
- continue;
21
- }
22
- if (SCHEMA_ARRAY_KEYWORDS.has(keyword) && Array.isArray(value)) {
23
- value.forEach((entry, index) => {
24
- if (isJsonSchema(entry)) {
25
- walk(entry, appendPointer(keywordPointer, index), depth + 1, keyword);
26
- }
27
- });
28
- continue;
29
- }
30
- if (SCHEMA_MAP_KEYWORDS.has(keyword) && isJsonObject(value)) {
31
- for (const [name, entry] of Object.entries(value)) {
32
- if (isJsonSchema(entry)) {
33
- walk(entry, appendPointer(keywordPointer, name), depth + 1, keyword);
34
- }
35
- }
36
- }
17
+ forEachSubschema(keyword, value, appendPointer(currentPointer, keyword), (entry, at) => walk(entry, at, depth + 1, keyword));
37
18
  }
38
19
  }
39
20
  finally {
@@ -43,6 +24,24 @@ export function walkSchema(schema, visit, pointer = '') {
43
24
  }
44
25
  walk(schema, pointer, 0);
45
26
  }
27
+ /** Visit each subschema `keyword` holds directly, with its pointer under `pointer`. */
28
+ export function forEachSubschema(keyword, value, pointer, visit) {
29
+ if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(value)) {
30
+ visit(value, pointer);
31
+ }
32
+ else if (SCHEMA_ARRAY_KEYWORDS.has(keyword) && Array.isArray(value)) {
33
+ value.forEach((entry, index) => {
34
+ if (isJsonSchema(entry))
35
+ visit(entry, appendPointer(pointer, index));
36
+ });
37
+ }
38
+ else if (SCHEMA_MAP_KEYWORDS.has(keyword) && isJsonObject(value)) {
39
+ for (const [name, entry] of Object.entries(value)) {
40
+ if (isJsonSchema(entry))
41
+ visit(entry, appendPointer(pointer, name));
42
+ }
43
+ }
44
+ }
46
45
  export function collectSchemaPointers(schema) {
47
46
  const pointers = new Set();
48
47
  walkSchema(schema, ({ pointer }) => pointers.add(pointer));