@astrale-os/kernel-dsl 1.0.0-beta.4 → 1.0.0-beta.6

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 (51) hide show
  1. package/dist/v1/builder/callable/authoring.d.ts +10 -4
  2. package/dist/v1/builder/callable/authoring.js +2 -0
  3. package/dist/v1/builder/class/authoring.d.ts +11 -1
  4. package/dist/v1/builder/class/authoring.js +11 -1
  5. package/dist/v1/builder/lowering/callable.js +1 -0
  6. package/dist/v1/builder/lowering/class.js +7 -0
  7. package/dist/v1/builder/schema/projection/callable.d.ts +7 -5
  8. package/dist/v1/builder/schema/projection/model.d.ts +4 -2
  9. package/dist/v1/builder/schema/projection/resolved-class.d.ts +2 -1
  10. package/dist/v1/compiled/admission/semantics/class.js +8 -1
  11. package/dist/v1/compiled/admission/wire/callable.js +11 -1
  12. package/dist/v1/compiled/admission/wire/class.js +24 -2
  13. package/dist/v1/compiled/compiler/compile.js +2 -0
  14. package/dist/v1/domain/create.js +1 -0
  15. package/dist/v1/domain/projection/classes.js +3 -0
  16. package/dist/v1/index.d.ts +1 -1
  17. package/dist/v1/language/callable/admission.d.ts +15 -0
  18. package/dist/v1/language/callable/admission.js +43 -0
  19. package/dist/v1/language/callable/model/callable.d.ts +10 -0
  20. package/dist/v1/language/callable/resolved.d.ts +5 -1
  21. package/dist/v1/language/class/inheritance.d.ts +9 -5
  22. package/dist/v1/language/class/inheritance.js +4 -0
  23. package/dist/v1/language/class/model/class.d.ts +9 -0
  24. package/dist/v1/language/class/model/edge.d.ts +11 -0
  25. package/dist/v1/language/index.d.ts +2 -0
  26. package/dist/v1/language/index.js +2 -0
  27. package/dist/v1/language/value/evaluate/validate.js +4 -0
  28. package/dist/v1/language/value/evaluate/validity.optimization.d.ts +14 -0
  29. package/dist/v1/language/value/evaluate/validity.optimization.js +342 -0
  30. package/dist/v1/language/value/profile/canonicalize.js +6 -5
  31. package/dist/v1/schema/compatibility/comparators/classes.d.ts +3 -0
  32. package/dist/v1/schema/compatibility/comparators/classes.js +4 -0
  33. package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
  34. package/dist/v1/schema/compatibility/fields.d.ts +20 -4
  35. package/dist/v1/schema/compatibility/fields.js +12 -3
  36. package/dist/v1/schema/compatibility/model.d.ts +4 -3
  37. package/dist/v1/schema/compatibility/structure/index.js +6 -4
  38. package/dist/v1/schema/compatibility/structure/requirements.js +29 -1
  39. package/dist/v1/schema/compatibility/versioning/surface.d.ts +1 -1
  40. package/dist/v1/schema/compatibility/versioning/surface.js +25 -2
  41. package/dist/v1/schema/diagnostic.d.ts +1 -1
  42. package/dist/v1/schema/normalization/domain.js +4 -0
  43. package/dist/v1/schema/validation/callable.d.ts +1 -1
  44. package/dist/v1/schema/validation/callable.js +17 -2
  45. package/dist/v1/schema/validation/class.js +10 -1
  46. package/dist/v1/schema/validation/domain.structure.js +29 -8
  47. package/dist/value/canonical.d.ts +5 -0
  48. package/dist/value/canonical.js +12 -0
  49. package/dist/value/json.d.ts +1 -1
  50. package/dist/value/json.js +1 -1
  51. package/package.json +1 -1
@@ -5,6 +5,11 @@ import type { DataFacet } from './data.js';
5
5
  import type { EdgeContract } from './edge.js';
6
6
  import type { PropertyFacet } from './property.js';
7
7
  export type ClassKind = 'node' | 'edge';
8
+ /**
9
+ * Which Classes may extend a Class: `'open'`, those of its Domain and of every Domain that depends
10
+ * on that Domain; `'internal'`, those of its Domain only; `'final'`, none.
11
+ */
12
+ export type Inheritance = 'open' | 'internal' | 'final';
8
13
  /** An absent observation rule admits every caller; a delegation is still scoped by its ceiling. */
9
14
  export interface NodeClassPolicies {
10
15
  readonly read?: NodeClassPolicy;
@@ -18,6 +23,8 @@ export interface EdgeClassPolicies {
18
23
  export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet, DataFacet {
19
24
  readonly kind: 'node';
20
25
  readonly abstract: boolean;
26
+ /** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
27
+ readonly inheritance?: Exclude<Inheritance, 'internal'>;
21
28
  readonly description?: string;
22
29
  readonly extends: readonly ClassRef[];
23
30
  readonly methods: MethodRecord;
@@ -27,6 +34,8 @@ export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyF
27
34
  export type EdgeClassDefinition = DefinitionBase<ClassRef> & PropertyFacet & EdgeContract & {
28
35
  readonly kind: 'edge';
29
36
  readonly abstract: boolean;
37
+ /** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
38
+ readonly inheritance?: Exclude<Inheritance, 'internal'>;
30
39
  readonly description?: string;
31
40
  readonly extends: readonly ClassRef[];
32
41
  readonly policies: EdgeClassPolicies;
@@ -22,6 +22,11 @@ export interface UndirectedEdgeConstraints {
22
22
  readonly noSelf?: true;
23
23
  readonly acyclic?: never;
24
24
  }
25
+ /**
26
+ * The fields one uniqueness tuple of a directed Edge Class names, which no two Edges of the Classes
27
+ * it covers share: its source endpoint and its slug.
28
+ */
29
+ export type EdgeUniqueTuple = readonly ['source', 'slug'];
25
30
  export interface DirectedEdgeContract {
26
31
  readonly orientation: 'directed';
27
32
  readonly endpoints: {
@@ -29,10 +34,16 @@ export interface DirectedEdgeContract {
29
34
  readonly target: DirectedTargetEndpoint;
30
35
  };
31
36
  readonly constraints: DirectedEdgeConstraints;
37
+ /**
38
+ * The tuples no two Edges of this Class and of its active concrete descendants share. Absent
39
+ * when the Class declares none, so a Class without one keeps its revision; never empty.
40
+ */
41
+ readonly unique?: readonly EdgeUniqueTuple[];
32
42
  }
33
43
  export interface UndirectedEdgeContract {
34
44
  readonly orientation: 'undirected';
35
45
  readonly endpoints: readonly [IncidentEndpoint, IncidentEndpoint];
36
46
  readonly constraints: UndirectedEdgeConstraints;
47
+ readonly unique?: never;
37
48
  }
38
49
  export type EdgeContract = DirectedEdgeContract | UndirectedEdgeContract;
@@ -1,3 +1,4 @@
1
+ export { admissionInputFields, isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from './callable/admission.js';
1
2
  export { isMethodExecutable } from './callable/executable.js';
2
3
  export { isBinaryInput } from './callable/model/callable.js';
3
4
  export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, FunctionRef, MethodKey, PolicyRef, PropertyKey, ViewRef, } from '../addressing/index.js';
@@ -13,6 +14,7 @@ export type * from './callable/resolved.js';
13
14
  export type * from './class/model/class.js';
14
15
  export type { DataDefinition, DataFacet, MediaType } from './class/model/data.js';
15
16
  export type * from './class/model/edge.js';
17
+ export { inheritanceOf } from './class/inheritance.js';
16
18
  export { edgeLifetimes } from './class/lifetime.js';
17
19
  export type { EdgeLifetime } from './class/lifetime.js';
18
20
  export type * from './class/model/property.js';
@@ -1,7 +1,9 @@
1
+ export { admissionInputFields, isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from './callable/admission.js';
1
2
  export { isMethodExecutable } from './callable/executable.js';
2
3
  export { isBinaryInput } from './callable/model/callable.js';
3
4
  export { valueProgramIncludes } from './value/inclusion/includes.js';
4
5
  export { stateMachine } from './state/index.js';
6
+ export { inheritanceOf } from './class/inheritance.js';
5
7
  export { edgeLifetimes } from './class/lifetime.js';
6
8
  export { PropertyValues } from './class/property-values.js';
7
9
  export { isClassRuleChecks, policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './policy/model/policy.js';
@@ -2,6 +2,7 @@ import { diagnostic } from '../../validation/diagnostic.js';
2
2
  import { validateCanonicalJsonDomain } from '../profile/json.validate.js';
3
3
  import { evaluateInstance } from './evaluator.js';
4
4
  import { prepareValueSchema, } from './preparation.js';
5
+ import { satisfies } from './validity.optimization.js';
5
6
  const validatorCache = new WeakMap();
6
7
  /**
7
8
  * Validate one complete portable value with the admitted V1 Draft 2020-12 profile.
@@ -30,6 +31,9 @@ export function validatePreparedInstance(compiled, value) {
30
31
  : instanceDiagnostics(compiled, value, '');
31
32
  }
32
33
  function instanceDiagnostics(compiled, value, pointer) {
34
+ // A value the compiled check finds valid has nothing to report; any other is evaluated.
35
+ if (satisfies(compiled, value) === true)
36
+ return [];
33
37
  const result = evaluateInstance(compiled, value);
34
38
  if (result.valid)
35
39
  return [];
@@ -0,0 +1,14 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { CompiledInstanceSchema } from './types.js';
3
+ /**
4
+ * Whether a value satisfies its prepared schema, as the evaluator decides it, without building a
5
+ * failure, an instance pointer or an annotation: the evaluator's verdict, or undefined when this
6
+ * check cannot give it. Only the evaluator reports why a value fails, so a caller keeps it for
7
+ * every value this check does not find valid.
8
+ *
9
+ * Each schema location is compiled once into a closure over its keywords, retained with the
10
+ * prepared schema. A schema whose closure holds an annotation-dependent keyword
11
+ * (`unevaluatedItems`, `unevaluatedProperties`) is left to the evaluator, as is a value nested
12
+ * deeper than this check follows, and any value whose check throws.
13
+ */
14
+ export declare function satisfies(compiled: CompiledInstanceSchema, value: JsonValue): boolean | undefined;
@@ -0,0 +1,342 @@
1
+ import { isCanonicalBlobRef } from '../model/blob.js';
2
+ import { isInstantValue } from '../model/instant.js';
3
+ import { isValueObject as isJsonObject } from '../model/object.js';
4
+ import { codePointLength, isMultipleOf, jsonEqual, matchesType, unique } from './assertions.js';
5
+ /**
6
+ * Whether a value satisfies its prepared schema, as the evaluator decides it, without building a
7
+ * failure, an instance pointer or an annotation: the evaluator's verdict, or undefined when this
8
+ * check cannot give it. Only the evaluator reports why a value fails, so a caller keeps it for
9
+ * every value this check does not find valid.
10
+ *
11
+ * Each schema location is compiled once into a closure over its keywords, retained with the
12
+ * prepared schema. A schema whose closure holds an annotation-dependent keyword
13
+ * (`unevaluatedItems`, `unevaluatedProperties`) is left to the evaluator, as is a value nested
14
+ * deeper than this check follows, and any value whose check throws.
15
+ */
16
+ export function satisfies(compiled, value) {
17
+ let checks = compilations.get(compiled);
18
+ if (checks === undefined) {
19
+ checks = annotationFree(compiled) ? new Map() : null;
20
+ compilations.set(compiled, checks);
21
+ }
22
+ if (checks === null)
23
+ return undefined;
24
+ const outer = verdicts;
25
+ verdicts = new Map();
26
+ try {
27
+ return checkOf(compiled, checks, compiled.root)(value, 0);
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ finally {
33
+ verdicts = outer;
34
+ }
35
+ }
36
+ /** Per prepared schema: its checks by location, or null when its closure needs annotations. */
37
+ const compilations = new WeakMap();
38
+ /** Locations one evaluation follows before leaving the value to the evaluator. */
39
+ const MAXIMUM_DEPTH = 1_024;
40
+ const UNDECIDED = new Error('The value is left to the evaluator.');
41
+ /**
42
+ * The verdicts of the current evaluation by referenced location and value. Only a reference reaches
43
+ * one location from several places, so each referenced location is checked once per value, as the
44
+ * evaluator memoizes it, and a schema that references a location twice per level stays linear. A
45
+ * location entered again on the same value is a cycle, left to the evaluator.
46
+ */
47
+ let verdicts = new Map();
48
+ function annotationFree(compiled) {
49
+ for (const { schema } of compiled.locations.values()) {
50
+ if (isJsonObject(schema) &&
51
+ (Object.hasOwn(schema, 'unevaluatedItems') || Object.hasOwn(schema, 'unevaluatedProperties'))) {
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ }
57
+ /** The check of one location, compiled on its first use so that references may recur. */
58
+ function checkOf(compiled, checks, location) {
59
+ let check = checks.get(location);
60
+ if (check === undefined) {
61
+ let body;
62
+ check = (value, depth) => {
63
+ if (depth > MAXIMUM_DEPTH)
64
+ throw UNDECIDED;
65
+ body ??= compileLocation(compiled, checks, location);
66
+ return body(value, depth);
67
+ };
68
+ checks.set(location, check);
69
+ }
70
+ return check;
71
+ }
72
+ /**
73
+ * The conjunction of the location's keywords, each as the evaluator applies it: an applicator
74
+ * checks the same value at its child location, a keyword of one type passes every other type.
75
+ */
76
+ function compileLocation(compiled, checks, location) {
77
+ const schema = location.schema;
78
+ if (schema === true)
79
+ return () => true;
80
+ if (schema === false)
81
+ return () => false;
82
+ const child = (keyword, member) => checkOf(compiled, checks, compiled.child(location, keyword, member));
83
+ const parts = [];
84
+ if (Object.hasOwn(schema, '$ref')) {
85
+ const target = compiled.reference(location);
86
+ if (target === undefined)
87
+ throw UNDECIDED;
88
+ const check = checkOf(compiled, checks, target);
89
+ parts.push((value, depth) => {
90
+ let byValue = verdicts.get(target);
91
+ if (byValue === undefined)
92
+ verdicts.set(target, (byValue = new Map()));
93
+ const known = byValue.get(value);
94
+ if (known === null)
95
+ throw UNDECIDED;
96
+ if (known !== undefined)
97
+ return known;
98
+ byValue.set(value, null);
99
+ const verdict = check(value, depth + 1);
100
+ byValue.set(value, verdict);
101
+ return verdict;
102
+ });
103
+ }
104
+ compileCompositions(schema, child, parts);
105
+ compileCommon(schema, parts);
106
+ compileNumber(schema, parts);
107
+ compileString(compiled, schema, parts);
108
+ compileArray(schema, child, parts);
109
+ compileObject(compiled, schema, child, parts);
110
+ if (parts.length === 0)
111
+ return () => true;
112
+ if (parts.length === 1)
113
+ return parts[0];
114
+ return (value, depth) => {
115
+ for (const part of parts)
116
+ if (!part(value, depth))
117
+ return false;
118
+ return true;
119
+ };
120
+ }
121
+ function compileCompositions(schema, child, parts) {
122
+ const allOf = schemaList(schema.allOf)?.map((_, index) => child('allOf', index));
123
+ if (allOf !== undefined) {
124
+ parts.push((value, depth) => allOf.every((check) => check(value, depth + 1)));
125
+ }
126
+ const anyOf = schemaList(schema.anyOf)?.map((_, index) => child('anyOf', index));
127
+ if (anyOf !== undefined) {
128
+ parts.push((value, depth) => anyOf.some((check) => check(value, depth + 1)));
129
+ }
130
+ const oneOf = schemaList(schema.oneOf)?.map((_, index) => child('oneOf', index));
131
+ if (oneOf !== undefined) {
132
+ parts.push((value, depth) => {
133
+ let admitted = 0;
134
+ for (const check of oneOf)
135
+ if (check(value, depth + 1) && ++admitted > 1)
136
+ return false;
137
+ return admitted === 1;
138
+ });
139
+ }
140
+ if (isSchema(schema.not)) {
141
+ const not = child('not');
142
+ parts.push((value, depth) => !not(value, depth + 1));
143
+ }
144
+ if (isSchema(schema.if)) {
145
+ const condition = child('if');
146
+ const then = isSchema(schema.then) ? child('then') : undefined;
147
+ const otherwise = isSchema(schema.else) ? child('else') : undefined;
148
+ parts.push((value, depth) => {
149
+ const branch = condition(value, depth + 1) ? then : otherwise;
150
+ return branch === undefined || branch(value, depth + 1);
151
+ });
152
+ }
153
+ }
154
+ function compileCommon(schema, parts) {
155
+ const kind = schema['x-astrale-value-kind'];
156
+ if (kind === 'instant')
157
+ parts.push((value) => isInstantValue(value));
158
+ if (kind === 'blob')
159
+ parts.push((value) => isCanonicalBlobRef(value));
160
+ const type = schema.type;
161
+ if (typeof type === 'string')
162
+ parts.push((value) => matchesType(type, value));
163
+ else if (Array.isArray(type)) {
164
+ const types = type.filter((candidate) => typeof candidate === 'string');
165
+ parts.push((value) => types.some((candidate) => matchesType(candidate, value)));
166
+ }
167
+ if (Object.hasOwn(schema, 'const')) {
168
+ const expected = schema.const;
169
+ parts.push((value) => jsonEqual(value, expected));
170
+ }
171
+ const choices = schema.enum;
172
+ if (Array.isArray(choices)) {
173
+ parts.push((value) => choices.some((candidate) => jsonEqual(value, candidate)));
174
+ }
175
+ }
176
+ function compileNumber(schema, parts) {
177
+ const { multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum } = schema;
178
+ const bounded = typeof multipleOf === 'number' ||
179
+ typeof maximum === 'number' ||
180
+ typeof exclusiveMaximum === 'number' ||
181
+ typeof minimum === 'number' ||
182
+ typeof exclusiveMinimum === 'number';
183
+ if (!bounded)
184
+ return;
185
+ parts.push((value) => typeof value !== 'number' ||
186
+ ((typeof multipleOf !== 'number' || isMultipleOf(value, multipleOf)) &&
187
+ (typeof maximum !== 'number' || value <= maximum) &&
188
+ (typeof exclusiveMaximum !== 'number' || value < exclusiveMaximum) &&
189
+ (typeof minimum !== 'number' || value >= minimum) &&
190
+ (typeof exclusiveMinimum !== 'number' || value > exclusiveMinimum)));
191
+ }
192
+ function compileString(compiled, schema, parts) {
193
+ const { maxLength, minLength, pattern, fullMatch } = schema;
194
+ if (typeof maxLength === 'number' || typeof minLength === 'number') {
195
+ // For n UTF-16 units, ceil(n / 2) <= code points <= n: count only when the bounds cannot
196
+ // decide, as the evaluator does.
197
+ parts.push((value) => {
198
+ if (typeof value !== 'string')
199
+ return true;
200
+ if ((typeof maxLength !== 'number' || value.length <= maxLength) &&
201
+ (typeof minLength !== 'number' || Math.ceil(value.length / 2) >= minLength)) {
202
+ return true;
203
+ }
204
+ const length = codePointLength(value);
205
+ return ((typeof maxLength !== 'number' || length <= maxLength) &&
206
+ (typeof minLength !== 'number' || length >= minLength));
207
+ });
208
+ }
209
+ if (typeof pattern === 'string') {
210
+ const matcher = compiled.matcher(pattern);
211
+ parts.push((value) => typeof value !== 'string' || matcher.test(value));
212
+ }
213
+ if (typeof fullMatch === 'string') {
214
+ const matcher = compiled.matcher(fullMatch);
215
+ parts.push((value) => typeof value !== 'string' || matcher.matches(value));
216
+ }
217
+ }
218
+ function compileArray(schema, child, parts) {
219
+ const { maxItems, minItems } = schema;
220
+ if (typeof maxItems === 'number' || typeof minItems === 'number' || schema.uniqueItems === true) {
221
+ const distinct = schema.uniqueItems === true;
222
+ parts.push((value) => !Array.isArray(value) ||
223
+ ((typeof maxItems !== 'number' || value.length <= maxItems) &&
224
+ (typeof minItems !== 'number' || value.length >= minItems) &&
225
+ (!distinct || unique(value))));
226
+ }
227
+ const prefix = schemaList(schema.prefixItems)?.map((_, index) => child('prefixItems', index));
228
+ const items = isSchema(schema.items) ? child('items') : undefined;
229
+ if (prefix !== undefined || items !== undefined) {
230
+ const leading = prefix ?? [];
231
+ parts.push((value, depth) => {
232
+ if (!Array.isArray(value))
233
+ return true;
234
+ for (let index = 0; index < value.length; index++) {
235
+ const check = index < leading.length ? leading[index] : items;
236
+ if (check !== undefined && !check(value[index], depth + 1))
237
+ return false;
238
+ }
239
+ return true;
240
+ });
241
+ }
242
+ if (isSchema(schema.contains)) {
243
+ const contains = child('contains');
244
+ const least = typeof schema.minContains === 'number' ? schema.minContains : 1;
245
+ const most = typeof schema.maxContains === 'number' ? schema.maxContains : Number.POSITIVE_INFINITY;
246
+ parts.push((value, depth) => {
247
+ if (!Array.isArray(value))
248
+ return true;
249
+ let matches = 0;
250
+ for (const item of value)
251
+ if (contains(item, depth + 1))
252
+ matches++;
253
+ return matches >= least && matches <= most;
254
+ });
255
+ }
256
+ }
257
+ function compileObject(compiled, schema, child, parts) {
258
+ const { maxProperties, minProperties } = schema;
259
+ const required = Array.isArray(schema.required)
260
+ ? schema.required.filter((name) => typeof name === 'string')
261
+ : [];
262
+ const dependentRequired = isJsonObject(schema.dependentRequired)
263
+ ? Object.entries(schema.dependentRequired).flatMap(([name, names]) => Array.isArray(names)
264
+ ? [[name, names.filter((other) => typeof other === 'string')]]
265
+ : [])
266
+ : [];
267
+ if (typeof maxProperties === 'number' ||
268
+ typeof minProperties === 'number' ||
269
+ required.length > 0 ||
270
+ dependentRequired.length > 0) {
271
+ parts.push((value) => {
272
+ if (!isJsonObject(value))
273
+ return true;
274
+ if (typeof maxProperties === 'number' || typeof minProperties === 'number') {
275
+ const count = Object.keys(value).length;
276
+ if (typeof maxProperties === 'number' && count > maxProperties)
277
+ return false;
278
+ if (typeof minProperties === 'number' && count < minProperties)
279
+ return false;
280
+ }
281
+ for (const name of required)
282
+ if (!Object.hasOwn(value, name))
283
+ return false;
284
+ for (const [name, names] of dependentRequired) {
285
+ if (Object.hasOwn(value, name) && !names.every((other) => Object.hasOwn(value, other))) {
286
+ return false;
287
+ }
288
+ }
289
+ return true;
290
+ });
291
+ }
292
+ // Each member is checked by every pattern it matches, by its declared schema, and by the
293
+ // additional schema when neither declares it.
294
+ const declared = isJsonObject(schema.properties) ? schema.properties : {};
295
+ const properties = new Map(Object.keys(declared).map((name) => [name, child('properties', name)]));
296
+ const patterns = isJsonObject(schema.patternProperties)
297
+ ? Object.keys(schema.patternProperties).map((source) => [compiled.matcher(source), child('patternProperties', source)])
298
+ : [];
299
+ const additional = isSchema(schema.additionalProperties)
300
+ ? child('additionalProperties')
301
+ : undefined;
302
+ const names = isSchema(schema.propertyNames) ? child('propertyNames') : undefined;
303
+ if (properties.size > 0 || patterns.length > 0 || additional !== undefined || names) {
304
+ parts.push((value, depth) => {
305
+ if (!isJsonObject(value))
306
+ return true;
307
+ for (const name of Object.keys(value)) {
308
+ const member = value[name];
309
+ let matched = false;
310
+ for (const [matcher, check] of patterns) {
311
+ if (!matcher.test(name))
312
+ continue;
313
+ matched = true;
314
+ if (!check(member, depth + 1))
315
+ return false;
316
+ }
317
+ const check = Object.hasOwn(declared, name) ? properties.get(name) : undefined;
318
+ if (check !== undefined) {
319
+ if (!check(member, depth + 1))
320
+ return false;
321
+ }
322
+ else if (!matched && additional !== undefined && !additional(member, depth + 1)) {
323
+ return false;
324
+ }
325
+ if (names !== undefined && !names(name, depth + 1))
326
+ return false;
327
+ }
328
+ return true;
329
+ });
330
+ }
331
+ if (isJsonObject(schema.dependentSchemas)) {
332
+ const dependent = Object.keys(schema.dependentSchemas).map((name) => [name, child('dependentSchemas', name)]);
333
+ parts.push((value, depth) => !isJsonObject(value) ||
334
+ dependent.every(([name, check]) => !Object.hasOwn(value, name) || check(value, depth + 1)));
335
+ }
336
+ }
337
+ function schemaList(input) {
338
+ return Array.isArray(input) ? input : undefined;
339
+ }
340
+ function isSchema(input) {
341
+ return typeof input === 'boolean' || isJsonObject(input);
342
+ }
@@ -7,10 +7,11 @@ const TYPE_RANK = new Map(TYPE_ORDER.map((name, index) => [name, index]));
7
7
  const encoder = new TextEncoder();
8
8
  /** Canonicalizes syntax without rewriting the schema's algebra. Validate first. */
9
9
  export function canonicalizeValueSchema(schema, context, registry) {
10
+ void context;
10
11
  void registry;
11
- return canonicalizeSchema(schema, true, context);
12
+ return canonicalizeSchema(schema, true);
12
13
  }
13
- function canonicalizeSchema(schema, root, context) {
14
+ function canonicalizeSchema(schema, root) {
14
15
  if (typeof schema === 'boolean')
15
16
  return schema;
16
17
  const result = {};
@@ -39,17 +40,17 @@ function canonicalizeSchema(schema, root, context) {
39
40
  result[keyword] = canonicalizePathAnnotation(value);
40
41
  }
41
42
  else if (SCHEMA_SINGLE_KEYWORDS.has(keyword) && isJsonSchema(value)) {
42
- result[keyword] = canonicalizeSchema(value, false, context);
43
+ result[keyword] = canonicalizeSchema(value, false);
43
44
  }
44
45
  else if (SCHEMA_ARRAY_KEYWORDS.has(keyword) && Array.isArray(value)) {
45
- result[keyword] = value.map((entry) => isJsonSchema(entry) ? canonicalizeSchema(entry, false, context) : canonicalizeJson(entry));
46
+ result[keyword] = value.map((entry) => isJsonSchema(entry) ? canonicalizeSchema(entry, false) : canonicalizeJson(entry));
46
47
  }
47
48
  else if (SCHEMA_MAP_KEYWORDS.has(keyword) && isJsonObject(value)) {
48
49
  const schemas = {};
49
50
  for (const name of Object.keys(value).sort()) {
50
51
  const entry = value[name];
51
52
  schemas[name] = isJsonSchema(entry)
52
- ? canonicalizeSchema(entry, false, context)
53
+ ? canonicalizeSchema(entry, false)
53
54
  : canonicalizeJson(entry);
54
55
  }
55
56
  result[keyword] = schemas;
@@ -1,5 +1,6 @@
1
1
  import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
2
  import type { Key as SemanticKey } from '../../../addressing/index.js';
3
+ import type { Inheritance } from '../../../language/class/model/class.js';
3
4
  import type { EffectiveField } from '../../analysis/class.js';
4
5
  import type { Context } from '../context.js';
5
6
  import type { ClassCoordinate } from '../coordinate.js';
@@ -38,6 +39,8 @@ export declare function compareLostProperties({ pair, report, }: {
38
39
  readonly pair: SubjectPair;
39
40
  readonly report: Report;
40
41
  }): void;
42
+ /** Which Classes may extend a Class, as an extension facet compares it. */
43
+ export declare function inheritanceMeaning(inheritance: Inheritance): JsonValue;
41
44
  /** Newly required data is an obligation even though it was absent from the source footprint. */
42
45
  export declare function compareAddedProperties({ context, pair, report, }: {
43
46
  readonly context: ComparisonContext;
@@ -139,6 +139,10 @@ function propertyIdentities(identities) {
139
139
  properties: Object.fromEntries(identities.map((identity) => [identity, true])),
140
140
  });
141
141
  }
142
+ /** Which Classes may extend a Class, as an extension facet compares it. */
143
+ export function inheritanceMeaning(inheritance) {
144
+ return deepFreeze({ coordinate: 'class', inheritance });
145
+ }
142
146
  /** Newly required data is an obligation even though it was absent from the source footprint. */
143
147
  export function compareAddedProperties({ context, pair, report, }) {
144
148
  if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
@@ -5,7 +5,7 @@ import type { ComparisonContext, SubjectPair } from '../scope.js';
5
5
  export declare function compareFacet({ context, pair, facet, }: {
6
6
  readonly context: ComparisonContext;
7
7
  readonly pair: SubjectPair;
8
- readonly facet: Exclude<Facet, 'class.composition' | 'class.properties' | 'property.required'>;
8
+ readonly facet: Exclude<Facet, 'class.composition' | 'class.properties' | 'class.inheritance' | 'property.required'>;
9
9
  }): {
10
10
  readonly before?: Value;
11
11
  readonly after?: Value;
@@ -15,7 +15,7 @@ export type DependencyMeaningLevel = 'reference' | 'inherit';
15
15
  * compile until it is classified, so meaning can only be narrowed by an explicit decision.
16
16
  */
17
17
  export type FieldRole = 'always' | 'inherit' | 'members' | {
18
- readonly excluded: 'identity' | 'annotation' | 'authorization' | 'presentation';
18
+ readonly excluded: 'identity' | 'annotation' | 'authorization' | 'extension' | 'presentation';
19
19
  };
20
20
  /** The coordinate Key already names the declaration; its `ref` is never repeated in the meaning. */
21
21
  export declare const CLASS_FIELDS: Readonly<{
@@ -24,12 +24,16 @@ export declare const CLASS_FIELDS: Readonly<{
24
24
  }>;
25
25
  kind: "always";
26
26
  abstract: "always";
27
+ inheritance: Readonly<{
28
+ excluded: 'extension';
29
+ }>;
27
30
  orientation: "always";
28
31
  extends: "inherit";
29
32
  policies: "inherit";
30
33
  data: "inherit";
31
34
  endpoints: "inherit";
32
35
  constraints: "inherit";
36
+ unique: "always";
33
37
  properties: "members";
34
38
  methods: "members";
35
39
  description: Readonly<{
@@ -50,9 +54,11 @@ export declare const PROPERTY_FIELDS: Readonly<{
50
54
  }>;
51
55
  }>;
52
56
  /**
53
- * The auth level and Policy check are decided by the Runtime at invocation, never compiled against.
54
- * The execution modifier decides what an invocation returns; an accepted Schema carries it only
55
- * when durable, so ordinary callables keep their meaning unchanged.
57
+ * The auth level, the Policy check and the admission age are authorization: decided by the Runtime
58
+ * at invocation, never compiled against. The admission age only bounds how old the admission a
59
+ * destination decides a call under may be. The execution modifier decides what an invocation
60
+ * returns; an accepted Schema carries it only when durable, so ordinary callables keep their meaning
61
+ * unchanged.
56
62
  */
57
63
  export declare const CALLABLE_FIELDS: Readonly<{
58
64
  ref: Readonly<{
@@ -63,6 +69,9 @@ export declare const CALLABLE_FIELDS: Readonly<{
63
69
  static: "always";
64
70
  abstract: "always";
65
71
  execution: "always";
72
+ admission: Readonly<{
73
+ excluded: 'authorization';
74
+ }>;
66
75
  auth: Readonly<{
67
76
  excluded: 'authorization';
68
77
  }>;
@@ -114,12 +123,16 @@ export declare const FIELD_ROLES: Readonly<{
114
123
  }>;
115
124
  kind: "always";
116
125
  abstract: "always";
126
+ inheritance: Readonly<{
127
+ excluded: 'extension';
128
+ }>;
117
129
  orientation: "always";
118
130
  extends: "inherit";
119
131
  policies: "inherit";
120
132
  data: "inherit";
121
133
  endpoints: "inherit";
122
134
  constraints: "inherit";
135
+ unique: "always";
123
136
  properties: "members";
124
137
  methods: "members";
125
138
  description: Readonly<{
@@ -148,6 +161,9 @@ export declare const FIELD_ROLES: Readonly<{
148
161
  static: "always";
149
162
  abstract: "always";
150
163
  execution: "always";
164
+ admission: Readonly<{
165
+ excluded: 'authorization';
166
+ }>;
151
167
  auth: Readonly<{
152
168
  excluded: 'authorization';
153
169
  }>;
@@ -1,18 +1,24 @@
1
1
  const IDENTITY = Object.freeze({ excluded: 'identity' });
2
2
  const ANNOTATION = Object.freeze({ excluded: 'annotation' });
3
3
  const AUTHORIZATION = Object.freeze({ excluded: 'authorization' });
4
+ /** Who may extend a Class is no meaning a dependent compiles against. */
5
+ const EXTENSION = Object.freeze({ excluded: 'extension' });
4
6
  const PRESENTATION = Object.freeze({ excluded: 'presentation' });
5
7
  /** The coordinate Key already names the declaration; its `ref` is never repeated in the meaning. */
6
8
  export const CLASS_FIELDS = Object.freeze({
7
9
  ref: IDENTITY,
8
10
  kind: 'always',
9
11
  abstract: 'always',
12
+ inheritance: EXTENSION,
10
13
  orientation: 'always',
11
14
  extends: 'inherit',
12
15
  policies: 'inherit',
13
16
  data: 'inherit',
14
17
  endpoints: 'inherit',
15
18
  constraints: 'inherit',
19
+ // A uniqueness tuple bounds every write of the Classes it covers and what a step through them
20
+ // reaches, so every dependent that names the Class relies on it, not only one that extends it.
21
+ unique: 'always',
16
22
  properties: 'members',
17
23
  methods: 'members',
18
24
  description: ANNOTATION,
@@ -27,9 +33,11 @@ export const PROPERTY_FIELDS = Object.freeze({
27
33
  description: ANNOTATION,
28
34
  });
29
35
  /**
30
- * The auth level and Policy check are decided by the Runtime at invocation, never compiled against.
31
- * The execution modifier decides what an invocation returns; an accepted Schema carries it only
32
- * when durable, so ordinary callables keep their meaning unchanged.
36
+ * The auth level, the Policy check and the admission age are authorization: decided by the Runtime
37
+ * at invocation, never compiled against. The admission age only bounds how old the admission a
38
+ * destination decides a call under may be. The execution modifier decides what an invocation
39
+ * returns; an accepted Schema carries it only when durable, so ordinary callables keep their meaning
40
+ * unchanged.
33
41
  */
34
42
  export const CALLABLE_FIELDS = Object.freeze({
35
43
  ref: IDENTITY,
@@ -38,6 +46,7 @@ export const CALLABLE_FIELDS = Object.freeze({
38
46
  static: 'always',
39
47
  abstract: 'always',
40
48
  execution: 'always',
49
+ admission: AUTHORIZATION,
41
50
  auth: AUTHORIZATION,
42
51
  policy: AUTHORIZATION,
43
52
  description: ANNOTATION,