@astrale-os/kernel-dsl 0.2.0-beta.46 → 0.2.0-beta.48

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 (159) hide show
  1. package/dist/v1/addressing/patterns.d.ts +0 -5
  2. package/dist/v1/addressing/patterns.js +5 -5
  3. package/dist/v1/builder/callable/authoring.d.ts +1 -1
  4. package/dist/v1/builder/class/authoring.d.ts +0 -12
  5. package/dist/v1/builder/class/property.d.ts +4 -2
  6. package/dist/v1/builder/class/property.js +9 -1
  7. package/dist/v1/builder/index.d.ts +1 -1
  8. package/dist/v1/builder/lowering/callable.d.ts +2 -1
  9. package/dist/v1/builder/lowering/core.d.ts +3 -2
  10. package/dist/v1/builder/lowering/schema/compile.js +15 -2
  11. package/dist/v1/builder/lowering/value/compile.js +3 -0
  12. package/dist/v1/builder/lowering/value/zod.d.ts +3 -2
  13. package/dist/v1/builder/lowering/view.d.ts +4 -3
  14. package/dist/v1/builder/lowering/view.js +2 -14
  15. package/dist/v1/builder/schema/define.d.ts +4 -2
  16. package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
  17. package/dist/v1/builder/schema/handle.d.ts +2 -1
  18. package/dist/v1/builder/schema/input.d.ts +8 -0
  19. package/dist/v1/builder/schema/projection/callable.d.ts +51 -0
  20. package/dist/v1/builder/schema/projection/callable.js +1 -0
  21. package/dist/v1/builder/schema/projection/class.d.ts +89 -0
  22. package/dist/v1/builder/schema/projection/class.js +1 -0
  23. package/dist/v1/builder/schema/projection/core-input.d.ts +32 -0
  24. package/dist/v1/builder/schema/projection/core-input.js +1 -0
  25. package/dist/v1/builder/schema/projection/domain.d.ts +59 -0
  26. package/dist/v1/builder/schema/projection/domain.js +1 -0
  27. package/dist/v1/builder/schema/projection/identity.d.ts +39 -0
  28. package/dist/v1/builder/schema/projection/identity.js +1 -0
  29. package/dist/v1/builder/schema/projection/model.d.ts +59 -0
  30. package/dist/v1/builder/schema/projection/model.js +1 -0
  31. package/dist/v1/builder/schema/projection/property.d.ts +32 -0
  32. package/dist/v1/builder/schema/projection/property.js +1 -0
  33. package/dist/v1/builder/schema/projection/resolved-class.d.ts +76 -0
  34. package/dist/v1/builder/schema/projection/resolved-class.js +1 -0
  35. package/dist/v1/builder/schema/projection.d.ts +4 -375
  36. package/dist/v1/builder/view/authoring.d.ts +3 -7
  37. package/dist/v1/builder/view/authoring.js +5 -2
  38. package/dist/v1/bundle/structure.d.ts +2 -1
  39. package/dist/v1/compiled/admission/accept.d.ts +1 -1
  40. package/dist/v1/compiled/admission/accept.js +7 -1001
  41. package/dist/v1/compiled/admission/closure.d.ts +3 -0
  42. package/dist/v1/compiled/admission/closure.js +173 -0
  43. package/dist/v1/compiled/admission/diagnostic.d.ts +7 -0
  44. package/dist/v1/compiled/admission/diagnostic.js +13 -0
  45. package/dist/v1/compiled/admission/revision.js +2 -0
  46. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +5 -0
  47. package/dist/v1/compiled/admission/semantics/cardinality.js +113 -0
  48. package/dist/v1/compiled/admission/semantics/class.d.ts +5 -0
  49. package/dist/v1/compiled/admission/semantics/class.js +125 -0
  50. package/dist/v1/compiled/admission/semantics/context.d.ts +60 -0
  51. package/dist/v1/compiled/admission/semantics/context.js +81 -0
  52. package/dist/v1/compiled/admission/semantics/core.d.ts +35 -0
  53. package/dist/v1/compiled/admission/semantics/core.js +123 -0
  54. package/dist/v1/compiled/admission/semantics/edge.d.ts +8 -0
  55. package/dist/v1/compiled/admission/semantics/edge.js +89 -0
  56. package/dist/v1/compiled/admission/semantics/policy/admit.d.ts +4 -0
  57. package/dist/v1/compiled/admission/semantics/policy/admit.js +57 -0
  58. package/dist/v1/compiled/admission/semantics/policy/callable.d.ts +5 -0
  59. package/dist/v1/compiled/admission/semantics/policy/callable.js +72 -0
  60. package/dist/v1/compiled/admission/semantics/policy/contracts.d.ts +24 -0
  61. package/dist/v1/compiled/admission/semantics/policy/contracts.js +45 -0
  62. package/dist/v1/compiled/admission/semantics/policy/equality.d.ts +8 -0
  63. package/dist/v1/compiled/admission/semantics/policy/equality.js +27 -0
  64. package/dist/v1/compiled/admission/semantics/policy/measure.d.ts +9 -0
  65. package/dist/v1/compiled/admission/semantics/policy/measure.js +81 -0
  66. package/dist/v1/compiled/admission/semantics/policy/parity.d.ts +4 -0
  67. package/dist/v1/compiled/admission/semantics/policy/parity.js +126 -0
  68. package/dist/v1/compiled/admission/semantics/policy/pattern.d.ts +8 -0
  69. package/dist/v1/compiled/admission/semantics/policy/pattern.js +78 -0
  70. package/dist/v1/compiled/admission/semantics/policy/typing.d.ts +28 -0
  71. package/dist/v1/compiled/admission/semantics/policy/typing.js +206 -0
  72. package/dist/v1/compiled/admission/semantics/policy/variants.d.ts +10 -0
  73. package/dist/v1/compiled/admission/semantics/policy/variants.js +65 -0
  74. package/dist/v1/compiled/admission/semantics.d.ts +1 -1
  75. package/dist/v1/compiled/admission/semantics.js +8 -1214
  76. package/dist/v1/compiled/admission/wire/callable.d.ts +6 -0
  77. package/dist/v1/compiled/admission/wire/callable.js +90 -0
  78. package/dist/v1/compiled/admission/wire/class.d.ts +4 -0
  79. package/dist/v1/compiled/admission/wire/class.js +117 -0
  80. package/dist/v1/compiled/admission/wire/core.d.ts +5 -0
  81. package/dist/v1/compiled/admission/wire/core.js +32 -0
  82. package/dist/v1/compiled/admission/wire/dependency.d.ts +14 -0
  83. package/dist/v1/compiled/admission/wire/dependency.js +43 -0
  84. package/dist/v1/compiled/admission/wire/entry.d.ts +5 -0
  85. package/dist/v1/compiled/admission/wire/entry.js +100 -0
  86. package/dist/v1/compiled/admission/wire/pattern.d.ts +12 -0
  87. package/dist/v1/compiled/admission/wire/pattern.js +111 -0
  88. package/dist/v1/compiled/admission/wire/policy.d.ts +5 -0
  89. package/dist/v1/compiled/admission/wire/policy.js +168 -0
  90. package/dist/v1/compiled/admission/wire/program.d.ts +4 -0
  91. package/dist/v1/compiled/admission/wire/program.js +52 -0
  92. package/dist/v1/compiled/admission/wire/property.d.ts +4 -0
  93. package/dist/v1/compiled/admission/wire/property.js +74 -0
  94. package/dist/v1/compiled/admission/wire/reference.d.ts +8 -0
  95. package/dist/v1/compiled/admission/wire/reference.js +33 -0
  96. package/dist/v1/compiled/admission/wire/value.d.ts +29 -0
  97. package/dist/v1/compiled/admission/wire/value.js +98 -0
  98. package/dist/v1/compiled/admission/wire/view.d.ts +3 -0
  99. package/dist/v1/compiled/admission/wire/view.js +17 -0
  100. package/dist/v1/compiled/compiler/compile.js +3 -0
  101. package/dist/v1/compiled/model/schema.d.ts +5 -1
  102. package/dist/v1/domain/create.js +0 -19
  103. package/dist/v1/domain/model/domain.d.ts +0 -1
  104. package/dist/v1/domain/projection/classes.js +2 -9
  105. package/dist/v1/index.d.ts +1 -1
  106. package/dist/v1/language/class/inheritance.d.ts +12 -0
  107. package/dist/v1/language/class/inheritance.js +25 -0
  108. package/dist/v1/language/class/model/property.d.ts +3 -0
  109. package/dist/v1/language/class/resolved.d.ts +5 -1
  110. package/dist/v1/language/definition/metadata.d.ts +8 -0
  111. package/dist/v1/language/definition/metadata.js +12 -0
  112. package/dist/v1/language/value/model/blob.d.ts +0 -2
  113. package/dist/v1/language/value/model/blob.js +2 -2
  114. package/dist/v1/language/value/model/instant.d.ts +0 -2
  115. package/dist/v1/language/value/model/instant.js +1 -1
  116. package/dist/v1/language/value/model/schema.d.ts +2 -1
  117. package/dist/v1/language/value/model/schema.js +1 -5
  118. package/dist/v1/language/value/reference/registry.d.ts +2 -1
  119. package/dist/v1/language/value/regexp/syntax.d.ts +2 -1
  120. package/dist/v1/language/view/model/view.d.ts +2 -4
  121. package/dist/v1/language/view/resolved.d.ts +2 -2
  122. package/dist/v1/schema/analysis/class.d.ts +4 -2
  123. package/dist/v1/schema/analysis/class.js +1 -0
  124. package/dist/v1/schema/canonical/revision.d.ts +2 -1
  125. package/dist/v1/schema/compatibility/comparators/classes.d.ts +2 -1
  126. package/dist/v1/schema/compatibility/context.d.ts +6 -2
  127. package/dist/v1/schema/compatibility/context.js +12 -6
  128. package/dist/v1/schema/compatibility/coordinate.d.ts +2 -1
  129. package/dist/v1/schema/compatibility/fields.d.ts +2 -0
  130. package/dist/v1/schema/compatibility/fields.js +1 -0
  131. package/dist/v1/schema/compatibility/references.js +1 -10
  132. package/dist/v1/schema/compatibility/report.d.ts +2 -1
  133. package/dist/v1/schema/compatibility/scope.d.ts +0 -4
  134. package/dist/v1/schema/compatibility/scope.js +1 -1
  135. package/dist/v1/schema/diagnostic.d.ts +1 -1
  136. package/dist/v1/schema/model/schema.d.ts +5 -1
  137. package/dist/v1/schema/normalization/domain.js +12 -6
  138. package/dist/v1/schema/resolution/definition.index.d.ts +1 -2
  139. package/dist/v1/schema/resolution/definition.index.js +0 -3
  140. package/dist/v1/schema/resolution/domain.d.ts +4 -1
  141. package/dist/v1/schema/resolution/domain.js +6 -7
  142. package/dist/v1/schema/resolution/origins.d.ts +1 -1
  143. package/dist/v1/schema/resolution/origins.js +0 -5
  144. package/dist/v1/schema/validation/class.js +19 -50
  145. package/dist/v1/schema/validation/core.js +1 -27
  146. package/dist/v1/schema/validation/domain.d.ts +2 -1
  147. package/dist/v1/schema/validation/domain.structure.d.ts +0 -2
  148. package/dist/v1/schema/validation/domain.structure.js +9 -8
  149. package/dist/v1/schema/validation/policy/context.js +5 -16
  150. package/dist/v1/schema/validation/view.js +14 -9
  151. package/dist/value/accept.d.ts +1 -1
  152. package/dist/value/accept.js +32 -6
  153. package/dist/value/canonical.js +10 -2
  154. package/dist/value/immutable.optimization.d.ts +4 -1
  155. package/dist/value/immutable.optimization.js +8 -2
  156. package/dist/value/limits.d.ts +1 -1
  157. package/dist/value/unicode.d.ts +3 -0
  158. package/dist/value/unicode.js +27 -0
  159. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { Key } from '../../addressing/key.js';
2
2
  import { definitionRefIndex } from '../../addressing/ordering.js';
3
3
  import { isMethodExecutable } from '../../language/callable/executable.js';
4
+ import { classSatisfies as satisfiesClass, isGraphRoot } from '../../language/class/inheritance.js';
4
5
  import { admitStateTopology } from '../../language/state/topology.js';
5
6
  import { isBlobValueSchema } from '../../language/value/model/blob.js';
6
7
  import { classifyOrdering } from '../../language/value/ordering.js';
@@ -62,6 +63,15 @@ function validateFacet(environment, declaration, pointer, diagnostics) {
62
63
  diagnostics.push(diagnostic('DV_PROPERTY_STATE', `${propertyPointer}/state`, error instanceof Error ? error.message : 'Invalid State topology.'));
63
64
  }
64
65
  }
66
+ if (property.immutable !== undefined) {
67
+ const immutablePointer = `${propertyPointer}/immutable`;
68
+ if (property.auto !== undefined || property.state !== undefined) {
69
+ diagnostics.push(diagnostic('DV_PROPERTY_IMMUTABLE', immutablePointer, 'An immutable Property is neither automatic nor a State Property.'));
70
+ }
71
+ if (property.immutable === 'once' && property.required) {
72
+ diagnostics.push(diagnostic('DV_PROPERTY_IMMUTABLE', immutablePointer, 'A required immutable Property is written at creation: declare it create, not once.'));
73
+ }
74
+ }
65
75
  if (property.auto === undefined)
66
76
  continue;
67
77
  const autoPointer = `${propertyPointer}/auto`;
@@ -105,10 +115,6 @@ function validateEdgeContract(environment, owner, declaration, diagnostics) {
105
115
  validateEndpoint(environment, owner, endpoints[0], declaration.orientation === 'directed' ? 'source' : 'first endpoint', diagnostics);
106
116
  validateEndpoint(environment, owner, endpoints[1], declaration.orientation === 'directed' ? 'target' : 'second endpoint', diagnostics);
107
117
  if (declaration.orientation === 'undirected') {
108
- if (!sameIncidentEndpointContract(declaration.endpoints[0], declaration.endpoints[1]) &&
109
- undirectedEndpointsOverlap(environment, declaration.endpoints)) {
110
- diagnostics.push(diagnostic('DV_EDGE_UNDIRECTED', '', `${formatDefinitionRef(owner)} has unequal undirected endpoint contracts whose accepted Node families overlap.`));
111
- }
112
118
  if (declaration.constraints.acyclic === true) {
113
119
  diagnostics.push(diagnostic('DV_EDGE_CONSTRAINT', '', 'acyclic is valid only for directed Edge contracts.'));
114
120
  }
@@ -151,6 +157,8 @@ function validateEdgeClassComposition(environment, diagnostics) {
151
157
  }
152
158
  /** Whether `child` safely refines its parent Edge contract `parent`. */
153
159
  export function edgeContractRefines(environment, child, parent) {
160
+ if (isGraphRoot(parent))
161
+ return true;
154
162
  if (child.orientation !== parent.orientation)
155
163
  return false;
156
164
  if (parent.constraints.noSelf === true && child.constraints.noSelf !== true)
@@ -186,57 +194,18 @@ function countRefines(child, parent) {
186
194
  const [parentMinimum, parentMaximum] = bounds[parent];
187
195
  return childMinimum >= parentMinimum && childMaximum <= parentMaximum;
188
196
  }
189
- function sameIncidentEndpointContract(left, right) {
190
- if (left.incident !== right.incident)
191
- return false;
192
- const a = [...left.accepts].map(definitionRefIndex).sort();
193
- const b = [...right.accepts].map(definitionRefIndex).sort();
194
- return a.length === b.length && a.every((value, index) => value === b[index]);
195
- }
196
- function undirectedEndpointsOverlap(environment, endpoints) {
197
- for (const resolved of environment.definitions.values()) {
198
- if (resolved.ref.kind !== 'class' ||
199
- resolved.declaration.kind !== 'node' ||
200
- resolved.declaration.abstract) {
201
- continue;
202
- }
203
- const candidate = resolved.ref;
204
- if (classSatisfies(environment, candidate, endpoints[0].accepts) &&
205
- classSatisfies(environment, candidate, endpoints[1].accepts)) {
206
- return true;
207
- }
208
- }
209
- return false;
210
- }
211
197
  /** Whether `candidate` is, or is a Node Class extending, one of `accepts`. */
212
198
  export function classSatisfies(environment, candidate, accepts) {
213
199
  if (accepts.some((ref) => definitionRefIndex(ref) === definitionRefIndex(candidate)))
214
200
  return true;
215
- const resolved = environment.resolveAny(candidate);
216
- if (resolved === undefined ||
217
- resolved.ref.kind !== 'class' ||
218
- resolved.declaration.kind !== 'node') {
201
+ const resolve = (ref) => environment.resolveAny(ref)?.declaration;
202
+ const actual = resolve(candidate);
203
+ if (actual?.kind !== 'node')
219
204
  return false;
220
- }
221
- return extendsAny(environment, resolved.declaration.extends, accepts, new Set());
222
- }
223
- function extendsAny(environment, parents, accepts, visited) {
224
- const accepted = new Set(accepts.map(definitionRefIndex));
225
- for (const parent of parents) {
226
- const key = definitionRefIndex(parent);
227
- if (accepted.has(key))
228
- return true;
229
- if (visited.has(key))
230
- continue;
231
- visited.add(key);
232
- const resolved = environment.resolveAny(parent);
233
- if (resolved !== undefined &&
234
- resolved.ref.kind === 'class' &&
235
- extendsAny(environment, resolved.declaration.extends, accepts, visited)) {
236
- return true;
237
- }
238
- }
239
- return false;
205
+ return accepts.some((ref) => {
206
+ const required = resolve(ref);
207
+ return required !== undefined && satisfiesClass(actual, required, resolve);
208
+ });
240
209
  }
241
210
  function localRef(origin, name) {
242
211
  return { origin, kind: 'class', name };
@@ -4,6 +4,7 @@ import { isMethodExecutable } from '../../language/callable/executable.js';
4
4
  import { policyObjectKey, policyObjectOrigin, projectedMemberClass, } from '../../language/policy/model/policy.js';
5
5
  import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
6
6
  import { diagnostic } from '../diagnostic.js';
7
+ import { classSatisfies } from './class.js';
7
8
  const formatDefinitionRef = Key.of;
8
9
  export function validateCore(environment, analysis, registry) {
9
10
  const diagnostics = [];
@@ -286,33 +287,6 @@ function hasDirectedCycle(edges) {
286
287
  };
287
288
  return [...outgoing.keys()].some(visit);
288
289
  }
289
- function classSatisfies(environment, classRef, accepts) {
290
- if (accepts.some((ref) => definitionRefIndex(ref) === definitionRefIndex(classRef)))
291
- return true;
292
- const resolved = environment.resolveAny(classRef);
293
- if (resolved === undefined || resolved.ref.kind !== 'class' || resolved.kind !== 'node') {
294
- return false;
295
- }
296
- return classExtendsAny(environment, resolved.declaration.extends, accepts, new Set());
297
- }
298
- function classExtendsAny(environment, parents, accepts, visited) {
299
- const accepted = new Set(accepts.map(definitionRefIndex));
300
- for (const parent of parents) {
301
- const key = definitionRefIndex(parent);
302
- if (accepted.has(key))
303
- return true;
304
- if (visited.has(key))
305
- continue;
306
- visited.add(key);
307
- const resolved = environment.resolveAny(parent);
308
- if (resolved !== undefined &&
309
- resolved.ref.kind === 'class' &&
310
- classExtendsAny(environment, resolved.declaration.extends, accepts, visited)) {
311
- return true;
312
- }
313
- }
314
- return false;
315
- }
316
290
  function escapePointer(value) {
317
291
  return value.replaceAll('~', '~0').replaceAll('/', '~1');
318
292
  }
@@ -3,9 +3,10 @@ import type { DomainSchemaLookup } from '../dependency/lookup.js';
3
3
  import type { SchemaDiagnostic } from '../diagnostic.js';
4
4
  import type { DomainSchemaIR } from '../model/schema.js';
5
5
  import type { DomainResolutionContext } from '../resolution/context.js';
6
- export interface DomainSchemaValidation {
6
+ interface DomainSchemaValidation {
7
7
  readonly context: DomainResolutionContext;
8
8
  readonly analysis: DefinitionAnalysisResult;
9
9
  readonly diagnostics: readonly SchemaDiagnostic[];
10
10
  }
11
11
  export declare function validateDomainSchemaSemantics(schema: DomainSchemaIR, lookup: DomainSchemaLookup): DomainSchemaValidation;
12
+ export {};
@@ -1,4 +1,2 @@
1
- import { type ZodType } from 'zod';
2
1
  import type { DomainSchemaIR } from '../model/schema.js';
3
- export declare const domainSchemaStructuralSchema: ZodType<DomainSchemaIR>;
4
2
  export declare function parseDomainSchemaStructure(input: unknown): DomainSchemaIR;
@@ -17,6 +17,10 @@ const descriptionSchema = string()
17
17
  .min(metadata.description.minLength)
18
18
  .max(metadata.description.maxLength);
19
19
  const iconSchema = string().min(metadata.icon.minLength).max(metadata.icon.maxLength);
20
+ const domainNameSchema = string()
21
+ .min(metadata.name.minLength)
22
+ .max(metadata.name.maxLength)
23
+ .refine(metadata.isDomainNameText, 'Expected one trimmed line of text.');
20
24
  const jsonValueSchema = lazy(() => union([
21
25
  nullSchema(),
22
26
  boolean(),
@@ -43,6 +47,7 @@ const propertyDefinitionSchema = strictObject({
43
47
  on: uniqueArray(enumeration(['create', 'update'])).min(1),
44
48
  set: literal('instant'),
45
49
  }).optional(),
50
+ immutable: enumeration(['create', 'once']).optional(),
46
51
  description: descriptionSchema.optional(),
47
52
  state: stateTopologySchema.optional(),
48
53
  });
@@ -291,13 +296,7 @@ const policyDefinitionSchema = strictObject({
291
296
  expression: policyExpressionSchema,
292
297
  description: descriptionSchema.optional(),
293
298
  });
294
- const viewTargetSchema = discriminatedUnion('kind', [
295
- strictObject({ kind: literal('domain') }),
296
- strictObject({
297
- kind: literal('definition'),
298
- definitions: uniqueArray(classRefSchema).min(1),
299
- }),
300
- ]);
299
+ const viewTargetSchema = strictObject({ kind: literal('domain') });
301
300
  const viewDefinitionSchema = strictObject({
302
301
  ref: viewRefSchema,
303
302
  description: descriptionSchema.optional(),
@@ -322,6 +321,8 @@ const structuralSchema = strictObject({
322
321
  format: literal(DOMAIN_FORMAT),
323
322
  version: literal(DOMAIN_VERSION),
324
323
  origin: originSchema,
324
+ name: domainNameSchema,
325
+ entrypoint: viewRefSchema.optional(),
325
326
  dependencies: record(dependencyAliasSchema, domainDependencySchema),
326
327
  classes: record(memberNameSchema, classDeclarationSchema),
327
328
  functions: record(memberNameSchema, functionDefinitionSchema),
@@ -329,7 +330,7 @@ const structuralSchema = strictObject({
329
330
  views: record(memberNameSchema, viewDefinitionSchema),
330
331
  core: coreSchema,
331
332
  });
332
- export const domainSchemaStructuralSchema = structuralSchema;
333
+ const domainSchemaStructuralSchema = structuralSchema;
333
334
  export function parseDomainSchemaStructure(input) {
334
335
  const result = domainSchemaStructuralSchema.safeParse(input);
335
336
  if (result.success)
@@ -1,5 +1,6 @@
1
1
  import { Key } from '../../../addressing/index.js';
2
2
  import { definitionRefIndex } from '../../../addressing/ordering.js';
3
+ import { classSatisfies } from '../../../language/class/inheritance.js';
3
4
  import { diagnostic } from '../../diagnostic.js';
4
5
  const formatDefinitionRef = Key.of;
5
6
  export class PolicyValidationContext {
@@ -63,22 +64,10 @@ export class PolicyValidationContext {
63
64
  }
64
65
  /** Return true when candidate is the required Class or a same-kind descendant. */
65
66
  classExtends(candidate, required) {
66
- if (definitionRefIndex(candidate) === definitionRefIndex(required))
67
- return true;
68
- const visited = new Set();
69
- const visit = (ref) => {
70
- const key = definitionRefIndex(ref);
71
- if (key === definitionRefIndex(required))
72
- return true;
73
- if (visited.has(key))
74
- return false;
75
- visited.add(key);
76
- const resolved = this.environment.resolveAny(ref);
77
- if (resolved === undefined || resolved.ref.kind !== 'class')
78
- return false;
79
- return resolved.declaration.extends.some(visit);
80
- };
81
- return visit(candidate);
67
+ const resolve = (ref) => this.environment.resolveAny(ref)?.declaration;
68
+ const actual = resolve(candidate);
69
+ const target = resolve(required);
70
+ return actual !== undefined && target !== undefined && classSatisfies(actual, target, resolve);
82
71
  }
83
72
  add(code, pointer, message, ref) {
84
73
  this.diagnostics.push(diagnostic(code, pointer, message, ref));
@@ -9,21 +9,26 @@ export function validateViews(environment) {
9
9
  if (!sameRef(view.ref, ref)) {
10
10
  diagnostics.push(diagnostic('DV_REF_RESOLVES', `${pointer}/ref`, `View ref must be exactly ${formatDefinitionRef(ref)}.`, formatDefinitionRef(view.ref)));
11
11
  }
12
- if (view.target.kind === 'definition') {
13
- view.target.definitions.forEach((ref, index) => validateDefinitionTarget(environment, ref, `${pointer}/target/definitions/${index}`, diagnostics));
14
- }
15
12
  }
13
+ validateEntrypoint(environment, diagnostics);
16
14
  return Object.freeze(diagnostics);
17
15
  }
16
+ /** The entrypoint names one View this Domain declares for itself. */
17
+ function validateEntrypoint(environment, diagnostics) {
18
+ const { entrypoint, origin, views } = environment.candidate;
19
+ if (entrypoint === undefined)
20
+ return;
21
+ // An own member only: a name such as `constructor` must not resolve on the prototype.
22
+ const view = entrypoint.origin === origin && Object.hasOwn(views, entrypoint.name)
23
+ ? views[entrypoint.name]
24
+ : undefined;
25
+ if (view === undefined) {
26
+ diagnostics.push(diagnostic('DV_ENTRYPOINT', '/entrypoint', `Entrypoint ${formatDefinitionRef(entrypoint)} is not a Domain-targeted View of ${origin}.`, formatDefinitionRef(entrypoint)));
27
+ }
28
+ }
18
29
  function sameRef(left, right) {
19
30
  return left.origin === right.origin && left.kind === right.kind && left.name === right.name;
20
31
  }
21
- function validateDefinitionTarget(environment, ref, pointer, diagnostics) {
22
- const target = environment.resolve(ref);
23
- if (target === undefined || target.ref.kind !== 'class' || target.kind !== 'node') {
24
- diagnostics.push(diagnostic('DV_VIEW_TARGET', pointer, `Definition target ${formatDefinitionRef(ref)} does not resolve to a Node Class.`, formatDefinitionRef(ref)));
25
- }
26
- }
27
32
  function escapePointer(value) {
28
33
  return value.replaceAll('~', '~0').replaceAll('/', '~1');
29
34
  }
@@ -6,7 +6,7 @@ import { type Options } from './limits.js';
6
6
  * Every source property is obtained from one descriptor snapshot. Accessors are never invoked.
7
7
  */
8
8
  export declare function accept(input: unknown, options?: Options): Value;
9
- /** Admit unknown data into a deeply immutable snapshot; owned subtrees may be shared. */
9
+ /** Admit a canonically byte-bounded immutable snapshot; owned subtrees may be shared. */
10
10
  export declare function acceptImmutable(input: unknown, options?: Options): Value;
11
11
  /** Admit a read-only snapshot, sharing owned immutable subtrees without freezing new wrappers. */
12
12
  export declare function acceptForRead(input: unknown, options?: Options): Value;
@@ -1,7 +1,7 @@
1
1
  import { BoundaryError } from './error.js';
2
2
  import { includeSnapshot, retainImmutableSnapshot, reusableSnapshot, } from './immutable.optimization.js';
3
3
  import { limitsFor } from './limits.js';
4
- import { assertHostString } from './unicode.js';
4
+ import { assertHostString, canonicalPrimitiveBytes } from './unicode.js';
5
5
  /**
6
6
  * Admit an unknown host value and return a detached portable snapshot.
7
7
  *
@@ -10,7 +10,7 @@ import { assertHostString } from './unicode.js';
10
10
  export function accept(input, options) {
11
11
  return snapshot(input, options, 'copy');
12
12
  }
13
- /** Admit unknown data into a deeply immutable snapshot; owned subtrees may be shared. */
13
+ /** Admit a canonically byte-bounded immutable snapshot; owned subtrees may be shared. */
14
14
  export function acceptImmutable(input, options) {
15
15
  const captured = { propertyRoots: false };
16
16
  const value = snapshot(input, options, 'immutable', captured);
@@ -35,9 +35,10 @@ function snapshot(input, options, mode, captured) {
35
35
  active: new WeakSet(),
36
36
  limits: limitsFor(options),
37
37
  members: 0,
38
+ bytes: mode === 'immutable' && !captured?.propertyRoots ? 0 : undefined,
38
39
  mode,
39
40
  summary: mode === 'immutable' && !captured?.propertyRoots
40
- ? { members: 0, depth: 0, stringBytes: 0 }
41
+ ? { members: 0, depth: 0, stringBytes: 0, canonicalBytes: 0 }
41
42
  : undefined,
42
43
  summaryDepth: 0,
43
44
  propertyRoots: captured?.propertyRoots ?? false,
@@ -55,8 +56,10 @@ function snapshot(input, options, mode, captured) {
55
56
  }
56
57
  }
57
58
  function admit(input, pointer, depth, state) {
58
- if (input === null || typeof input === 'boolean')
59
+ if (input === null || typeof input === 'boolean') {
60
+ addCanonicalBytes(state, input === false ? 5 : 4, pointer);
59
61
  return input;
62
+ }
60
63
  if (typeof input === 'number') {
61
64
  if (!Number.isFinite(input)) {
62
65
  throw new BoundaryError('VALUE_NUMBER', 'Expected a finite IEEE-754 binary64 number', pointer);
@@ -64,6 +67,8 @@ function admit(input, pointer, depth, state) {
64
67
  if (Object.is(input, -0)) {
65
68
  throw new BoundaryError('VALUE_NUMBER', 'Negative zero is outside portable JSON', pointer);
66
69
  }
70
+ if (state.summary !== undefined || state.bytes !== undefined)
71
+ addCanonicalBytes(state, canonicalPrimitiveBytes(input), pointer);
67
72
  return input;
68
73
  }
69
74
  if (typeof input === 'string') {
@@ -74,9 +79,10 @@ function admit(input, pointer, depth, state) {
74
79
  throw new BoundaryError('VALUE_TYPE', 'Expected a portable value', pointer);
75
80
  }
76
81
  if (state.mode !== 'copy' && !(state.propertyRoots && depth === 0)) {
77
- const summary = reusableSnapshot(input, state.limits, depth, state.members);
82
+ const summary = reusableSnapshot(input, state.limits, depth, state.members, state.bytes);
78
83
  if (summary !== undefined) {
79
84
  state.members += summary.members;
85
+ chargeCanonicalBytes(state, summary.canonicalBytes, pointer);
80
86
  includeSnapshot(state.summary, summary, depth - state.summaryDepth);
81
87
  return input;
82
88
  }
@@ -92,7 +98,7 @@ function admit(input, pointer, depth, state) {
92
98
  const parentDepth = state.summaryDepth;
93
99
  const propertyRoot = state.mode === 'immutable' && state.propertyRoots && depth === 1;
94
100
  if (propertyRoot) {
95
- state.summary = { members: 0, depth: 0, stringBytes: 0 };
101
+ state.summary = { members: 0, depth: 0, stringBytes: 0, canonicalBytes: 0 };
96
102
  state.summaryDepth = depth;
97
103
  }
98
104
  if (state.summary !== undefined) {
@@ -129,6 +135,9 @@ function admitArray(input, pointer, depth, state) {
129
135
  if (keys.length !== size + 1 || keys.some((key) => typeof key === 'symbol')) {
130
136
  throw new BoundaryError('VALUE_ARRAY', 'Expected a dense Array with no symbol or non-index properties', pointer);
131
137
  }
138
+ // Every portable item needs at least one byte; refuse before the detached Array allocation.
139
+ requireCanonicalRoom(state, size === 0 ? 2 : size * 2 + 1, pointer);
140
+ addCanonicalBytes(state, 2 + Math.max(0, size - 1), pointer);
132
141
  const output = Array.from({ length: size }, () => null);
133
142
  for (let index = 0; index < size; index += 1) {
134
143
  const name = String(index);
@@ -151,6 +160,7 @@ function admitObject(input, pointer, depth, state) {
151
160
  const descriptors = reflect(() => Object.getOwnPropertyDescriptors(input), pointer, 'Cannot snapshot Object descriptors');
152
161
  const keys = Reflect.ownKeys(descriptors);
153
162
  addMembers(state, keys.length, pointer);
163
+ addCanonicalBytes(state, 2 + Math.max(0, keys.length - 1) + keys.length, pointer);
154
164
  const output = {};
155
165
  for (const key of keys) {
156
166
  if (typeof key === 'symbol') {
@@ -191,6 +201,22 @@ function admitString(value, pointer, state) {
191
201
  if (state.summary !== undefined) {
192
202
  state.summary.stringBytes = Math.max(state.summary.stringBytes, bytes);
193
203
  }
204
+ if (state.summary !== undefined || state.bytes !== undefined)
205
+ addCanonicalBytes(state, canonicalPrimitiveBytes(value), pointer);
206
+ }
207
+ function requireCanonicalRoom(state, bytes, pointer) {
208
+ if (state.bytes !== undefined && bytes > state.limits.maxBytes - state.bytes)
209
+ throw new BoundaryError('VALUE_LIMIT', `Canonical JSON exceeds the ${state.limits.maxBytes}-byte limit`, pointer);
210
+ }
211
+ function chargeCanonicalBytes(state, bytes, pointer) {
212
+ requireCanonicalRoom(state, bytes, pointer);
213
+ if (state.bytes !== undefined)
214
+ state.bytes += bytes;
215
+ }
216
+ function addCanonicalBytes(state, bytes, pointer) {
217
+ chargeCanonicalBytes(state, bytes, pointer);
218
+ if (state.summary !== undefined)
219
+ state.summary.canonicalBytes += bytes;
194
220
  }
195
221
  function reflect(operation, pointer, message) {
196
222
  try {
@@ -1,7 +1,8 @@
1
1
  import { acceptForRead } from './accept.js';
2
2
  import { BoundaryError } from './error.js';
3
+ import { immutableSnapshotBytes } from './immutable.optimization.js';
3
4
  import { defaultLimits, limitsFor } from './limits.js';
4
- import { utf8Length } from './unicode.js';
5
+ import { canonicalPrimitiveBytes, utf8Length } from './unicode.js';
5
6
  const encoder = new TextEncoder();
6
7
  /** RFC 8785 canonical JSON for one admitted portable snapshot. */
7
8
  export function serialize(input, options) {
@@ -13,9 +14,16 @@ export function serializeAdmitted(input) {
13
14
  return serializeSnapshot(input, defaultLimits.maxBytes);
14
15
  }
15
16
  function serializeSnapshot(input, maxBytes) {
17
+ const bytes = input !== null && typeof input === 'object'
18
+ ? immutableSnapshotBytes(input)
19
+ : canonicalPrimitiveBytes(input);
20
+ if (bytes !== undefined && bytes > maxBytes)
21
+ throw new BoundaryError('VALUE_LIMIT', `Canonical JSON exceeds the ${maxBytes}-byte limit`);
16
22
  const output = serializeValue(input);
17
23
  // Scalar text uses at most three UTF-8 bytes per UTF-16 code unit, including JSON escapes.
18
- if (output.length > maxBytes / 3 && utf8Length(output, maxBytes) > maxBytes) {
24
+ if (bytes === undefined &&
25
+ output.length > maxBytes / 3 &&
26
+ utf8Length(output, maxBytes) > maxBytes) {
19
27
  throw new BoundaryError('VALUE_LIMIT', `Canonical JSON exceeds the ${maxBytes}-byte limit`);
20
28
  }
21
29
  return output;
@@ -3,10 +3,13 @@ export interface SnapshotSummary {
3
3
  members: number;
4
4
  depth: number;
5
5
  stringBytes: number;
6
+ canonicalBytes: number;
6
7
  }
7
8
  export declare function isImmutableSnapshot(value: object): boolean;
9
+ /** Exact complete representation size, only for roots retained by this admission owner. */
10
+ export declare function immutableSnapshotBytes(value: object): number | undefined;
8
11
  /** Called only after admission has constructed and deeply frozen this container. */
9
12
  export declare function retainImmutableSnapshot(value: object, summary: SnapshotSummary): void;
10
13
  export declare function includeSnapshot(parent: SnapshotSummary | undefined, child: SnapshotSummary, depth: number): void;
11
14
  /** A failed fit must use normal admission, preserving its exact error pointer. */
12
- export declare function reusableSnapshot(value: object, limits: Limits, depth: number, members: number): SnapshotSummary | undefined;
15
+ export declare function reusableSnapshot(value: object, limits: Limits, depth: number, members: number, bytes: number | undefined): SnapshotSummary | undefined;
@@ -4,6 +4,10 @@ const snapshots = new WeakMap();
4
4
  export function isImmutableSnapshot(value) {
5
5
  return snapshots.has(value);
6
6
  }
7
+ /** Exact complete representation size, only for roots retained by this admission owner. */
8
+ export function immutableSnapshotBytes(value) {
9
+ return snapshots.get(value)?.canonicalBytes;
10
+ }
7
11
  /** Called only after admission has constructed and deeply frozen this container. */
8
12
  export function retainImmutableSnapshot(value, summary) {
9
13
  if (!snapshots.has(value))
@@ -15,14 +19,16 @@ export function includeSnapshot(parent, child, depth) {
15
19
  parent.members += child.members;
16
20
  parent.depth = Math.max(parent.depth, depth + child.depth);
17
21
  parent.stringBytes = Math.max(parent.stringBytes, child.stringBytes);
22
+ parent.canonicalBytes += child.canonicalBytes;
18
23
  }
19
24
  /** A failed fit must use normal admission, preserving its exact error pointer. */
20
- export function reusableSnapshot(value, limits, depth, members) {
25
+ export function reusableSnapshot(value, limits, depth, members, bytes) {
21
26
  const summary = snapshots.get(value);
22
27
  if (summary === undefined ||
23
28
  summary.members > limits.maxMembers - members ||
24
29
  summary.depth > limits.maxDepth - depth ||
25
- summary.stringBytes > limits.maxStringBytes)
30
+ summary.stringBytes > limits.maxStringBytes ||
31
+ (bytes !== undefined && summary.canonicalBytes > limits.maxBytes - bytes))
26
32
  return undefined;
27
33
  return summary;
28
34
  }
@@ -1,5 +1,5 @@
1
1
  export interface Limits {
2
- /** Maximum UTF-8 size accepted from or emitted to a wire boundary. */
2
+ /** Maximum wire UTF-8 size, also bounding the complete canonical immutable capture. */
3
3
  readonly maxBytes: number;
4
4
  /** Maximum number of nested Array/Object containers, with a root container at depth one. */
5
5
  readonly maxDepth: number;
@@ -1,3 +1,6 @@
1
+ import type { Primitive } from './types.js';
2
+ /** Exact canonical scalar spelling after the same owner has established portable input. */
3
+ export declare function canonicalPrimitiveBytes(value: Primitive): number;
1
4
  export declare function utf8Length(value: string, max?: number): number;
2
5
  export declare function assertHostString(value: string, pointer: string, maxBytes: number): number;
3
6
  export declare function assertDecodedString(value: string, offset: number, maxBytes: number): void;
@@ -1,4 +1,31 @@
1
1
  import { BoundaryError, DecodeError } from './error.js';
2
+ /** Exact canonical scalar spelling after the same owner has established portable input. */
3
+ export function canonicalPrimitiveBytes(value) {
4
+ if (typeof value !== 'string') {
5
+ if (typeof value === 'number')
6
+ return JSON.stringify(value).length;
7
+ return value === false ? 5 : 4;
8
+ }
9
+ let bytes = 2;
10
+ for (let index = 0; index < value.length; index += 1) {
11
+ const unit = value.charCodeAt(index);
12
+ if (unit < 0x20)
13
+ bytes += unit === 8 || unit === 9 || unit === 10 || unit === 12 || unit === 13 ? 2 : 6;
14
+ else if (unit === 0x22 || unit === 0x5c)
15
+ bytes += 2;
16
+ else if (unit <= 0x7f)
17
+ bytes += 1;
18
+ else if (unit <= 0x7ff)
19
+ bytes += 2;
20
+ else if (isHighSurrogate(unit)) {
21
+ bytes += 4;
22
+ index += 1;
23
+ }
24
+ else
25
+ bytes += 3;
26
+ }
27
+ return bytes;
28
+ }
2
29
  export function utf8Length(value, max = Number.MAX_SAFE_INTEGER) {
3
30
  let bytes = 0;
4
31
  for (let index = 0; index < value.length; index += 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-dsl",
3
- "version": "0.2.0-beta.46",
3
+ "version": "0.2.0-beta.48",
4
4
  "description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {