@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
@@ -46,7 +46,7 @@ export interface Usage {
46
46
  readonly key?: Key;
47
47
  readonly relation: ReferenceSite | 'member' | 'abstract' | 'selection' | 'capability';
48
48
  }
49
- export type Facet = 'class.reference' | 'class.inherit' | 'class.composition' | 'class.properties' | 'class.endpoints' | 'property' | 'property.required' | 'method.existence' | 'method.object' | 'method.inheritance' | 'method.signature' | 'function.existence' | 'function.signature' | 'policy' | 'view' | 'core';
49
+ export type Facet = 'class.reference' | 'class.inherit' | 'class.composition' | 'class.properties' | 'class.endpoints' | 'class.inheritance' | 'property' | 'property.required' | 'method.existence' | 'method.object' | 'method.inheritance' | 'method.signature' | 'function.existence' | 'function.signature' | 'policy' | 'view' | 'core';
50
50
  /** A fact about a compared facet, including unchanged facets. No admission decision is implied. */
51
51
  export interface Observation {
52
52
  readonly id: string;
@@ -81,8 +81,9 @@ export interface Assessment {
81
81
  * meaning evidence never records whether capabilities were declared. A referenced or inherited Class
82
82
  * that loses an effective Property (`class.properties`) raises structure to version 4 and meaning to
83
83
  * version 2, and structure is version 4 too when it records that a dependent Policy lost what it
84
- * relies on (`method.object`, `class.endpoints`). Those facts are recorded only on such a loss, so
85
- * every other comparison, every compatible one included, keeps its bytes.
84
+ * relies on (`method.object`, `class.endpoints`) or that a Class the dependent extends became final
85
+ * (`class.inheritance`). Those facts are recorded only on such a loss, so every other comparison,
86
+ * every compatible one included, keeps its bytes.
86
87
  */
87
88
  export interface ComparisonEvidence {
88
89
  readonly kind: 'structure' | 'meaning';
@@ -12,8 +12,9 @@ export function analyzeStructure(context) {
12
12
  ...result,
13
13
  compatible: result.assessments.every(({ satisfied }) => satisfied),
14
14
  // Each version adds one requirement policy and applies only when its fact is present: 3 a
15
- // declared capability root, 4 a lost effective Property of a reached Class or a failed Policy
16
- // reliance, facts recorded only when they fail. Without one, evidence keeps its version-2 bytes.
15
+ // declared capability root, 4 a lost effective Property of a reached Class, a failed Policy
16
+ // reliance or a refused extension, facts recorded only when they fail. Without one, evidence
17
+ // keeps its version-2 bytes.
17
18
  evidence: evidenceOf({
18
19
  kind: 'structure',
19
20
  version: recordsFailureOnlyFact(result.assessments)
@@ -27,13 +28,14 @@ export function analyzeStructure(context) {
27
28
  }
28
29
  /**
29
30
  * The facts structure records only when they fail: a referenced or inherited Class that lost an
30
- * effective Property, and what a dependent Policy relies on (a Method object, a matched Edge's
31
- * endpoints).
31
+ * effective Property, what a dependent Policy relies on (a Method object, a matched Edge's
32
+ * endpoints), and a Class the dependent extends that became final.
32
33
  */
33
34
  const FAILURE_ONLY_FACETS = new Set([
34
35
  'class.properties',
35
36
  'method.object',
36
37
  'class.endpoints',
38
+ 'class.inheritance',
37
39
  ]);
38
40
  function recordsFailureOnlyFact(assessments) {
39
41
  return assessments.some(({ requirement }) => FAILURE_ONLY_FACETS.has(requirement.kind));
@@ -1,5 +1,6 @@
1
1
  import { Key } from '../../../addressing/index.js';
2
- import { compareAddedProperties, compareLostProperties } from '../comparators/classes.js';
2
+ import { inheritanceOf } from '../../../language/class/inheritance.js';
3
+ import { compareAddedProperties, compareLostProperties, inheritanceMeaning, } from '../comparators/classes.js';
3
4
  import { compareFacet } from '../comparators/index.js';
4
5
  import { fingerprintMeaning } from '../comparators/values.js';
5
6
  import { contextInvalid } from '../context.js';
@@ -73,6 +74,7 @@ export function compareRequirements({ context, report, }) {
73
74
  compareLostProperties({ pair, report });
74
75
  }
75
76
  compareReliance({ context, report });
77
+ compareExtension({ context, report });
76
78
  }
77
79
  /**
78
80
  * What a dependent Policy relies on is recorded only when it fails: a Method it names that is gone
@@ -95,3 +97,29 @@ function compareReliance({ context, report, }) {
95
97
  report.record({ pair, facet, before, after, requirement: { kind: facet } });
96
98
  }
97
99
  }
100
+ /**
101
+ * A Class one of the dependent's own Classes extends must still admit it: the dependent is refused
102
+ * once the compared origin declares that Class `'final'`. The fact is recorded only when it fails,
103
+ * like a Policy reliance, so a compatible comparison keeps its evidence bytes.
104
+ */
105
+ function compareExtension({ context, report, }) {
106
+ for (const reference of context.roots) {
107
+ if (reference.usage.relation !== 'extends')
108
+ continue;
109
+ if (Key.origin(reference.coordinate.key) !== context.source.origin)
110
+ continue;
111
+ const pair = context.pair(reference.coordinate);
112
+ if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
113
+ continue;
114
+ if (pair.target.analysis.declaration.inheritance !== 'final')
115
+ continue;
116
+ report.note(reference);
117
+ report.record({
118
+ pair,
119
+ facet: 'class.inheritance',
120
+ before: inheritanceMeaning(inheritanceOf(pair.source.analysis.declaration)),
121
+ after: inheritanceMeaning('final'),
122
+ requirement: { kind: 'class.inheritance' },
123
+ });
124
+ }
125
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Report } from '../report.js';
2
2
  import type { ComparisonContext } from '../scope.js';
3
3
  /**
4
- * Compare inherited contracts and newly imposed obligations on existing Classes.
4
+ * Compare inherited contracts, newly imposed obligations and who may extend existing Classes.
5
5
  * Obligations are reported at their declaring Method: changed signatures retain their comparison;
6
6
  * an unchanged obligation newly inherited by a Class is added and unsatisfied.
7
7
  */
@@ -1,10 +1,11 @@
1
+ import { inheritanceOf } from '../../../language/class/inheritance.js';
1
2
  import { methodInheritance } from '../comparators/callables.js';
2
- import { compareAddedProperties } from '../comparators/classes.js';
3
+ import { compareAddedProperties, inheritanceMeaning } from '../comparators/classes.js';
3
4
  import { compareFacet } from '../comparators/index.js';
4
5
  import { fingerprintMeaning } from '../comparators/values.js';
5
6
  import { classCoordinate, methodCoordinate } from '../coordinate.js';
6
7
  /**
7
- * Compare inherited contracts and newly imposed obligations on existing Classes.
8
+ * Compare inherited contracts, newly imposed obligations and who may extend existing Classes.
8
9
  * Obligations are reported at their declaring Method: changed signatures retain their comparison;
9
10
  * an unchanged obligation newly inherited by a Class is added and unsatisfied.
10
11
  */
@@ -25,6 +26,7 @@ export function compareSurface({ context, report, }) {
25
26
  requirement: { kind: 'class.inherit' },
26
27
  });
27
28
  compareAddedProperties({ context, pair, report });
29
+ compareExtension({ pair, report });
28
30
  for (const [key, method] of source.analysis.requirements)
29
31
  if (!established.has(key))
30
32
  established.set(key, method);
@@ -54,6 +56,27 @@ export function compareSurface({ context, report, }) {
54
56
  });
55
57
  }
56
58
  }
59
+ /**
60
+ * A changed inheritance is observed on its Class, and is major when the Class was `'open'` or
61
+ * becomes `'final'`. Closing an `'open'` Class withdraws what its declaration granted other Domains;
62
+ * acceptance refuses a subclass of a `'final'` Class of any Domain and admits one of another
63
+ * Domain's `'internal'` Class (DV-CLASS-INHERITANCE), so finalizing that Class can refuse a dependent.
64
+ */
65
+ function compareExtension({ pair, report, }) {
66
+ if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
67
+ return;
68
+ const before = inheritanceOf(pair.source.analysis.declaration);
69
+ const after = inheritanceOf(pair.target.analysis.declaration);
70
+ if (before === after)
71
+ return;
72
+ report.record({
73
+ pair,
74
+ facet: 'class.inheritance',
75
+ before: inheritanceMeaning(before),
76
+ after: inheritanceMeaning(after),
77
+ requirement: { kind: 'class.inheritance', satisfied: before !== 'open' && after !== 'final' },
78
+ });
79
+ }
57
80
  function changed({ before, after }) {
58
81
  return (before === undefined ||
59
82
  after === undefined ||
@@ -1,4 +1,4 @@
1
- export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_FOREIGN_MINIMUM' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
1
+ export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_ADMISSION' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_INHERITANCE' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_FOREIGN_MINIMUM' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
2
2
  export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
3
3
  readonly code: Code;
4
4
  readonly pointer: string;
@@ -61,6 +61,7 @@ function canonicalizeClass(value) {
61
61
  ref: value.ref,
62
62
  kind: value.kind,
63
63
  abstract: value.abstract,
64
+ ...(value.inheritance === undefined ? {} : { inheritance: value.inheritance }),
64
65
  extends: [...value.extends].sort(compareDefinitionRefs),
65
66
  properties: mapRecord(value.properties, canonicalizeProperty),
66
67
  };
@@ -80,6 +81,7 @@ function canonicalizeClass(value) {
80
81
  endpoints: canonicalizeDirectedEndpoints(value.endpoints),
81
82
  policies: value.policies,
82
83
  constraints: value.constraints,
84
+ ...(value.unique === undefined ? {} : { unique: value.unique }),
83
85
  }
84
86
  : {
85
87
  ...base,
@@ -115,6 +117,8 @@ function canonicalizeCallable(value) {
115
117
  output: canonicalizeCallableOutput(value.output),
116
118
  // Ordinary execution has one canonical form, so existing revisions never move.
117
119
  ...(value.execution?.durable === true ? { execution: { durable: true } } : {}),
120
+ // Absence is the one spelling of a call admitted afresh; validation refuses any other.
121
+ ...(value.admission === undefined ? {} : { admission: { maxAge: value.admission.maxAge } }),
118
122
  };
119
123
  return value.auth === 'authorized'
120
124
  ? {
@@ -1,4 +1,4 @@
1
1
  import type { SchemaDiagnostic } from '../diagnostic.js';
2
2
  import type { DomainSchemaIR } from '../model/schema.js';
3
- /** Validate the execution modifier of every local Function and Node Method. */
3
+ /** Validate the execution modifier and admission age of every local Function and Node Method. */
4
4
  export declare function validateCallables(schema: DomainSchemaIR): readonly SchemaDiagnostic[];
@@ -1,10 +1,13 @@
1
+ import { isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from '../../language/callable/admission.js';
1
2
  import { isBinaryInput } from '../../language/callable/model/callable.js';
2
3
  import { diagnostic } from '../diagnostic.js';
3
- /** Validate the execution modifier of every local Function and Node Method. */
4
+ /** Validate the execution modifier and admission age of every local Function and Node Method. */
4
5
  export function validateCallables(schema) {
5
6
  const diagnostics = [];
6
7
  for (const [name, declaration] of Object.entries(schema.functions)) {
7
- validateExecution(declaration, `/functions/${escapePointer(name)}`, diagnostics);
8
+ const pointer = `/functions/${escapePointer(name)}`;
9
+ validateExecution(declaration, pointer, diagnostics);
10
+ validateAdmission(declaration, pointer, diagnostics);
8
11
  }
9
12
  for (const [name, declaration] of Object.entries(schema.classes)) {
10
13
  if (declaration.kind !== 'node')
@@ -12,10 +15,22 @@ export function validateCallables(schema) {
12
15
  for (const [method, value] of Object.entries(declaration.methods)) {
13
16
  const pointer = `/classes/${escapePointer(name)}/methods/${escapePointer(method)}`;
14
17
  validateExecution(value, pointer, diagnostics);
18
+ validateAdmission(value, pointer, diagnostics);
15
19
  }
16
20
  }
17
21
  return Object.freeze(diagnostics);
18
22
  }
23
+ /**
24
+ * An admission age is a whole number of seconds within the language ceiling. It has no zero
25
+ * spelling: an absent admission age is the one canonical form of a call admitted afresh.
26
+ */
27
+ function validateAdmission(declaration, pointer, diagnostics) {
28
+ if (declaration.admission === undefined)
29
+ return;
30
+ if (isAdmissionMaxAge(declaration.admission.maxAge))
31
+ return;
32
+ diagnostics.push(diagnostic('DV_CALLABLE_ADMISSION', `${pointer}/admission/maxAge`, `An admission age is an integer number of seconds from 1 to ${MAXIMUM_ADMISSION_AGE_SECONDS}; omit it to admit every call afresh.`));
33
+ }
19
34
  /**
20
35
  * An Execution records its initiating caller and its admitted input, from which the run is
21
36
  * resubmitted, and retains one portable outcome. A durable callable therefore needs a known caller,
@@ -87,9 +87,15 @@ function validateFacet(environment, declaration, pointer, diagnostics) {
87
87
  return;
88
88
  for (const [name, method] of Object.entries(declaration.methods)) {
89
89
  const methodPointer = `${pointer}/methods/${escapePointer(name)}`;
90
- if (!isMethodExecutable(declaration, method) && method.policy !== undefined) {
90
+ if (isMethodExecutable(declaration, method))
91
+ continue;
92
+ if (method.policy !== undefined) {
91
93
  diagnostics.push(diagnostic('DV_METHOD_ABSTRACT', `${methodPointer}/policy`, 'Abstract Methods cannot have a Policy.'));
92
94
  }
95
+ // An admission age qualifies the admission of an executable declaration, like its Policy.
96
+ if (method.admission !== undefined) {
97
+ diagnostics.push(diagnostic('DV_METHOD_ABSTRACT', `${methodPointer}/admission`, 'Abstract Methods cannot have an admission age.'));
98
+ }
93
99
  }
94
100
  }
95
101
  function validateParents(environment, owner, ownerKind, parents, diagnostics) {
@@ -103,6 +109,9 @@ function validateParents(environment, owner, ownerKind, parents, diagnostics) {
103
109
  if (target.ref.kind !== 'class' || target.declaration.kind !== ownerKind) {
104
110
  diagnostics.push(diagnostic('DV_CLASS_KIND', pointer, `${formatDefinitionRef(owner)} cannot extend different-kind Class ${formatDefinitionRef(parent)}.`, formatDefinitionRef(parent)));
105
111
  }
112
+ else if (target.declaration.inheritance === 'final') {
113
+ diagnostics.push(diagnostic('DV_CLASS_INHERITANCE', pointer, `${formatDefinitionRef(owner)} cannot extend final Class ${formatDefinitionRef(parent)}.`, formatDefinitionRef(parent)));
114
+ }
106
115
  }
107
116
  }
108
117
  function validateEdgeContract(environment, owner, declaration, diagnostics) {
@@ -98,6 +98,9 @@ const edgeConstraintsSchema = strictObject({
98
98
  noSelf: literal(true).optional(),
99
99
  acyclic: literal(true).optional(),
100
100
  });
101
+ // A tuple names the source and the slug. No tuple is spelled by the absence of the field, never by
102
+ // an empty list: one spelling per meaning, one revision.
103
+ const edgeUniqueSchema = uniqueArray(tuple([literal('source'), literal('slug')])).min(1);
101
104
  const policyPatternSubjectSchema = strictObject({ kind: literal('subject') });
102
105
  const policyPatternObjectSchema = strictObject({ kind: literal('object') });
103
106
  const policyPatternSourceSchema = strictObject({ kind: literal('source') });
@@ -195,11 +198,16 @@ const valueFunctionInputSchema = jsonSchemaSchema.refine((value) => typeof value
195
198
  const callableExecutionSchema = strictObject({
196
199
  durable: boolean(),
197
200
  });
201
+ // Its range is language meaning, refused by callable validation with DV_CALLABLE_ADMISSION.
202
+ const callableAdmissionSchema = strictObject({
203
+ maxAge: number(),
204
+ });
198
205
  const callableShape = {
199
206
  description: descriptionSchema.optional(),
200
207
  input: jsonSchemaSchema,
201
208
  output: callableOutputSchema,
202
209
  execution: callableExecutionSchema.optional(),
210
+ admission: callableAdmissionSchema.optional(),
203
211
  auth: enumeration(['anonymous', 'authenticated', 'authorized']),
204
212
  policy: union([literal(true), policyCheckExpressionSchema]).optional(),
205
213
  };
@@ -228,10 +236,13 @@ const edgeClassPoliciesSchema = strictObject({
228
236
  read: classPolicySchema.optional(),
229
237
  traverse: classPolicySchema.optional(),
230
238
  });
239
+ // The default `'internal'` is the absence of the field: one spelling per meaning, one revision.
240
+ const inheritanceSchema = enumeration(['open', 'final']);
231
241
  const nodeClassSchema = strictObject({
232
242
  ref: classRefSchema,
233
243
  kind: literal('node'),
234
244
  abstract: boolean(),
245
+ inheritance: inheritanceSchema.optional(),
235
246
  description: descriptionSchema.optional(),
236
247
  extends: uniqueArray(classRefSchema),
237
248
  properties: propertyRecordSchema,
@@ -244,10 +255,12 @@ const edgeClassShape = {
244
255
  ref: classRefSchema,
245
256
  kind: literal('edge'),
246
257
  abstract: boolean(),
258
+ inheritance: inheritanceSchema.optional(),
247
259
  description: descriptionSchema.optional(),
248
260
  extends: uniqueArray(classRefSchema),
249
261
  properties: propertyRecordSchema,
250
262
  constraints: edgeConstraintsSchema,
263
+ unique: edgeUniqueSchema.optional(),
251
264
  };
252
265
  const edgeClassSchema = strictObject({
253
266
  ...edgeClassShape,
@@ -257,14 +270,22 @@ const edgeClassSchema = strictObject({
257
270
  }).superRefine(validateEdgeOrientationShape);
258
271
  const classDeclarationSchema = discriminatedUnion('kind', [nodeClassSchema, edgeClassSchema]);
259
272
  function validateEdgeOrientationShape(value, context) {
260
- if ((value.orientation === 'undirected') === Array.isArray(value.endpoints))
261
- return;
262
- context.addIssue({
263
- code: 'custom',
264
- path: ['endpoints'],
265
- message: `${value.orientation} Edge endpoints have the wrong structural shape.`,
266
- params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
267
- });
273
+ if ((value.orientation === 'undirected') !== Array.isArray(value.endpoints)) {
274
+ context.addIssue({
275
+ code: 'custom',
276
+ path: ['endpoints'],
277
+ message: `${value.orientation} Edge endpoints have the wrong structural shape.`,
278
+ params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
279
+ });
280
+ }
281
+ if (value.orientation === 'undirected' && value.unique !== undefined) {
282
+ context.addIssue({
283
+ code: 'custom',
284
+ path: ['unique'],
285
+ message: 'Only a directed Edge Class declares unique tuples.',
286
+ params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
287
+ });
288
+ }
268
289
  }
269
290
  const functionDefinitionSchema = strictObject({
270
291
  ref: functionRefSchema,
@@ -5,3 +5,8 @@ export declare function serialize(input: unknown, options?: Options): string;
5
5
  /** Serialize a trusted portable value whose default structural admission proof is still valid. */
6
6
  export declare function serializeAdmitted(input: Value): string;
7
7
  export declare function encode(input: unknown, options?: Options): Uint8Array;
8
+ /**
9
+ * The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`: an
10
+ * admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
11
+ */
12
+ export declare function byteLength(input: Value): number;
@@ -31,6 +31,18 @@ function serializeSnapshot(input, maxBytes) {
31
31
  export function encode(input, options) {
32
32
  return encoder.encode(serialize(input, options));
33
33
  }
34
+ /**
35
+ * The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`: an
36
+ * admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
37
+ */
38
+ export function byteLength(input) {
39
+ if (input !== null && typeof input === 'object') {
40
+ const bytes = immutableSnapshotBytes(input);
41
+ if (bytes !== undefined)
42
+ return bytes;
43
+ }
44
+ return utf8Length(serializeAdmitted(input));
45
+ }
34
46
  function serializeValue(value) {
35
47
  if (value === null)
36
48
  return 'null';
@@ -1,3 +1,3 @@
1
- export { encode, serialize, serializeAdmitted } from './canonical.js';
1
+ export { byteLength, encode, serialize, serializeAdmitted } from './canonical.js';
2
2
  export { decode } from './decode.js';
3
3
  export { DecodeError } from './error.js';
@@ -1,3 +1,3 @@
1
- export { encode, serialize, serializeAdmitted } from './canonical.js';
1
+ export { byteLength, encode, serialize, serializeAdmitted } from './canonical.js';
2
2
  export { decode } from './decode.js';
3
3
  export { DecodeError } from './error.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-dsl",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {