@astrale-os/kernel-dsl 0.2.0-beta.36 → 0.2.0-beta.38

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 (58) hide show
  1. package/dist/v1/builder/core/authoring.d.ts +7 -2
  2. package/dist/v1/builder/lowering/schema/context.d.ts +9 -4
  3. package/dist/v1/builder/lowering/schema/context.js +20 -20
  4. package/dist/v1/builder/policy/authoring.d.ts +6 -6
  5. package/dist/v1/builder/policy/authoring.js +3 -1
  6. package/dist/v1/compiled/admission/accept.js +97 -18
  7. package/dist/v1/compiled/admission/semantics.js +77 -24
  8. package/dist/v1/compiled/compiler/compile.js +23 -7
  9. package/dist/v1/compiled/loader/load.d.ts +11 -2
  10. package/dist/v1/compiled/loader/load.js +4 -1
  11. package/dist/v1/compiled/model/schema.d.ts +5 -4
  12. package/dist/v1/domain/create.js +17 -9
  13. package/dist/v1/index.d.ts +1 -1
  14. package/dist/v1/language/class/property-values.js +5 -2
  15. package/dist/v1/language/core/model/core.d.ts +7 -1
  16. package/dist/v1/language/core/resolved.d.ts +6 -2
  17. package/dist/v1/language/index.d.ts +1 -1
  18. package/dist/v1/language/index.js +1 -1
  19. package/dist/v1/language/policy/index.d.ts +1 -1
  20. package/dist/v1/language/policy/index.js +1 -1
  21. package/dist/v1/language/policy/model/policy.d.ts +1 -0
  22. package/dist/v1/language/policy/model/policy.js +17 -0
  23. package/dist/v1/language/value/compile/fields.d.ts +1 -1
  24. package/dist/v1/language/value/compile/load.d.ts +4 -2
  25. package/dist/v1/language/value/compile/load.js +22 -1
  26. package/dist/v1/language/value/compile/model.d.ts +16 -1
  27. package/dist/v1/language/value/evaluate/assertions.d.ts +2 -0
  28. package/dist/v1/language/value/evaluate/assertions.js +13 -0
  29. package/dist/v1/language/value/evaluate/evaluator.js +12 -7
  30. package/dist/v1/language/value/evaluate/preparation.d.ts +10 -1
  31. package/dist/v1/language/value/evaluate/preparation.js +17 -26
  32. package/dist/v1/language/value/evaluate/program.js +1 -1
  33. package/dist/v1/language/value/evaluate/validate.d.ts +6 -2
  34. package/dist/v1/language/value/evaluate/validate.js +18 -11
  35. package/dist/v1/language/value/ordering.d.ts +2 -0
  36. package/dist/v1/language/value/ordering.js +4 -8
  37. package/dist/v1/language/value/profile/json.validate.js +1 -1
  38. package/dist/v1/language/value/regexp/compile.d.ts +3 -3
  39. package/dist/v1/language/value/regexp/compile.js +56 -11
  40. package/dist/v1/language/value/regexp/matcher.d.ts +3 -3
  41. package/dist/v1/language/value/regexp/matcher.js +75 -3
  42. package/dist/v1/schema/compatibility/footprint.js +6 -5
  43. package/dist/v1/schema/normalization/domain.js +15 -1
  44. package/dist/v1/schema/resolution/domain.js +17 -10
  45. package/dist/v1/schema/resolution/origins.d.ts +3 -0
  46. package/dist/v1/schema/resolution/origins.js +12 -4
  47. package/dist/v1/schema/validation/core.js +78 -26
  48. package/dist/v1/schema/validation/domain.structure.js +7 -2
  49. package/dist/value/accept.d.ts +9 -0
  50. package/dist/value/accept.js +78 -5
  51. package/dist/value/canonical.js +2 -2
  52. package/dist/value/immutable.optimization.d.ts +12 -0
  53. package/dist/value/immutable.optimization.js +28 -0
  54. package/dist/value/index.d.ts +1 -1
  55. package/dist/value/index.js +1 -1
  56. package/dist/value/unicode.d.ts +1 -1
  57. package/dist/value/unicode.js +1 -0
  58. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import type { Object as JsonObject } from '@astrale-os/kernel-dsl/value';
2
- import type { CoreRef } from '../../addressing/index.js';
3
2
  import type { DomainEndpointRef } from '../../language/core/model/core.js';
3
+ import type { PolicyObjectInput } from '../callable/authoring.js';
4
4
  import type { EdgeClass, NodeClass } from '../class/authoring.js';
5
5
  import type { AUTHORING } from '../schema/handle.js';
6
6
  import type { Lazy } from '../schema/lazy.js';
@@ -18,7 +18,12 @@ interface CoreEdgeConfiguration {
18
18
  export interface CoreNode {
19
19
  readonly [AUTHORING]: 'core-node';
20
20
  }
21
- export type CoreEndpoint = CoreNode | CoreRef | DomainEndpointRef;
21
+ /**
22
+ * The owning Domain, or any local Definition of it: a Class, Function, Method, Policy, View, or
23
+ * Core Node, authored as a handle, a lazy handle, or its canonical ref. A lazy handle lets a Core
24
+ * Edge name a declaration that only exists later in a module cycle.
25
+ */
26
+ export type CoreEndpoint = PolicyObjectInput | DomainEndpointRef;
22
27
  export interface CoreEdge {
23
28
  readonly [AUTHORING]: 'core-edge';
24
29
  }
@@ -1,4 +1,4 @@
1
- import type { CoreRef, ClassRef, DefinitionRef, FunctionRef, PolicyRef, ViewRef } from '../../../addressing/index.js';
1
+ import type { ClassRef, DefinitionRef, FunctionRef, PolicyRef, ViewRef } from '../../../addressing/index.js';
2
2
  import type { DomainDependency } from '../../../addressing/index.js';
3
3
  import type { ClassKind } from '../../../language/class/model/class.js';
4
4
  import type { CoreEndpointRef } from '../../../language/core/model/core.js';
@@ -7,7 +7,7 @@ import type { ValueSchemaRegistry } from '../../../language/value/reference/regi
7
7
  import type { DomainSchema, DomainSchemaIR } from '../../../schema/model/schema.js';
8
8
  import type { Function, PolicyObjectInput } from '../../callable/authoring.js';
9
9
  import type { Class, ClassReferenceInput } from '../../class/authoring.js';
10
- import type { CoreNode } from '../../core/authoring.js';
10
+ import type { CoreEndpoint, CoreNode } from '../../core/authoring.js';
11
11
  import type { Policy } from '../../policy/authoring.js';
12
12
  import type { View } from '../../view/authoring.js';
13
13
  import { type Lazy } from '../../schema/lazy.js';
@@ -19,7 +19,6 @@ export declare class BuilderContext {
19
19
  readonly localDefinitions: Map<string, DefinitionRef<import("../../../addressing/definition-ref.js").Definition.Kind, string>>;
20
20
  readonly localClassKinds: Map<string, ClassKind>;
21
21
  readonly policies: Map<object, PolicyRef>;
22
- readonly coreNodes: Map<CoreNode, CoreRef>;
23
22
  readonly policyObjects: Map<object, PolicyObjectRef>;
24
23
  readonly policyObjectRefs: Set<string>;
25
24
  readonly usedForeignOrigins: Set<string>;
@@ -34,7 +33,13 @@ export declare class BuilderContext {
34
33
  resolveEdgeClass(input: ClassReferenceInput, pointer: string): ClassRef;
35
34
  resolvePolicy(input: Lazy<unknown>, pointer: string): PolicyRef;
36
35
  resolvePolicyObject(input: PolicyObjectInput | PolicyObjectRef, pointer: string): PolicyObjectRef;
37
- resolveCoreEndpoint(value: CoreNode | CoreEndpointRef, pointer: string): CoreEndpointRef;
36
+ /** A Core fact belongs to the Domain that declares it, so a dependency endpoint never resolves. */
37
+ resolveCoreEndpoint(input: CoreEndpoint, pointer: string): CoreEndpointRef;
38
+ /**
39
+ * The shared lookup from one already-resolved authoring value to its ref; each caller owns its
40
+ * own admission rule and diagnostic.
41
+ */
42
+ private policyObjectRef;
38
43
  dependencies(): Readonly<Record<string, DomainDependency>>;
39
44
  valueRegistry(): ValueSchemaRegistry;
40
45
  noteDomainEndpointReferences(schema: DomainSchemaIR): void;
@@ -15,7 +15,6 @@ export class BuilderContext {
15
15
  localDefinitions = new Map();
16
16
  localClassKinds = new Map();
17
17
  policies = new Map();
18
- coreNodes = new Map();
19
18
  policyObjects = new Map();
20
19
  policyObjectRefs = new Set();
21
20
  usedForeignOrigins = new Set();
@@ -59,7 +58,6 @@ export class BuilderContext {
59
58
  captureCoreNode(value, slug, pointer) {
60
59
  const ref = { origin: this.origin, kind: 'core', name: slug };
61
60
  this.ownership.captureDefinition(value, ref, pointer);
62
- this.coreNodes.set(value, ref);
63
61
  this.capturePolicyObject(value, ref);
64
62
  }
65
63
  captureFunction(value, ref, pointer) {
@@ -110,12 +108,7 @@ export class BuilderContext {
110
108
  return { ...ref };
111
109
  }
112
110
  resolvePolicyObject(input, pointer) {
113
- const value = resolveLazy(input, pointer);
114
- const ref = isPolicyObjectRef(value)
115
- ? value
116
- : typeof value === 'object' && value !== null
117
- ? (this.policyObjects.get(value) ?? this.ownership.definition(value))
118
- : undefined;
111
+ const ref = this.policyObjectRef(resolveLazy(input, pointer));
119
112
  const origin = ref === undefined ? undefined : policyObjectOrigin(ref);
120
113
  const dependency = origin === undefined ? undefined : this.#dependencies.get(origin)?.schema;
121
114
  const admitted = ref !== undefined &&
@@ -130,15 +123,30 @@ export class BuilderContext {
130
123
  this.usedForeignOrigins.add(admittedOrigin);
131
124
  return { ...ref };
132
125
  }
133
- resolveCoreEndpoint(value, pointer) {
126
+ /** A Core fact belongs to the Domain that declares it, so a dependency endpoint never resolves. */
127
+ resolveCoreEndpoint(input, pointer) {
128
+ const value = resolveLazy(input, pointer);
134
129
  if (isDomainEndpointRef(value))
135
130
  return { kind: 'domain' };
136
- const ref = isCoreRef(value) ? value : this.coreNodes.get(value);
137
- if (ref === undefined || ref.origin !== this.origin) {
138
- fail('BUILDER_CORE_REF', pointer, 'A Core reference must name a registered local Core Node.');
131
+ const ref = this.policyObjectRef(value);
132
+ if (ref === undefined ||
133
+ policyObjectOrigin(ref) !== this.origin ||
134
+ !this.policyObjectRefs.has(policyObjectKey(ref))) {
135
+ fail('BUILDER_CORE_REF', pointer, 'A Core Edge endpoint must name one registered local Definition, Function, Method, Policy, View, or Core Node.');
139
136
  }
140
137
  return { ...ref };
141
138
  }
139
+ /**
140
+ * The shared lookup from one already-resolved authoring value to its ref; each caller owns its
141
+ * own admission rule and diagnostic.
142
+ */
143
+ policyObjectRef(value) {
144
+ if (isPolicyObjectRef(value))
145
+ return value;
146
+ return typeof value === 'object' && value !== null
147
+ ? (this.policyObjects.get(value) ?? this.ownership.definition(value))
148
+ : undefined;
149
+ }
142
150
  dependencies() {
143
151
  return Object.fromEntries([...this.#dependencies.values()]
144
152
  .sort((left, right) => compareUnicode(left.alias, right.alias))
@@ -226,14 +234,6 @@ function isDefinitionRef(value) {
226
234
  typeof candidate.name === 'string' &&
227
235
  candidate.kind === 'class');
228
236
  }
229
- function isCoreRef(value) {
230
- if (typeof value !== 'object' || value === null)
231
- return false;
232
- const candidate = value;
233
- return (typeof candidate.origin === 'string' &&
234
- candidate.kind === 'core' &&
235
- typeof candidate.name === 'string');
236
- }
237
237
  function isPolicyObjectRef(value) {
238
238
  if (typeof value !== 'object' || value === null)
239
239
  return false;
@@ -1,12 +1,12 @@
1
1
  import type { ClassRef } from '../../addressing/index.js';
2
2
  import type { PolicyCheckExpression, PolicyPatternExpression, PolicyPatternTerm as CanonicalPolicyPatternTerm, PolicyObjectRef } from '../../language/policy/model/policy.js';
3
3
  import type { PolicyObjectInput } from '../callable/authoring.js';
4
- import type { EdgeClass, NodeClass } from '../class/authoring.js';
4
+ import type { EdgeClassReferenceInput, NodeClassReferenceInput } from '../class/authoring.js';
5
5
  import type { AuthoringValue } from '../schema/handle.js';
6
6
  import type { Lazy } from '../schema/lazy.js';
7
7
  export interface PolicyEdgeInput {
8
8
  readonly source: CanonicalPolicyPatternTerm;
9
- readonly class: Lazy<EdgeClass>;
9
+ readonly class: EdgeClassReferenceInput;
10
10
  readonly target: CanonicalPolicyPatternTerm;
11
11
  readonly repeat?: {
12
12
  readonly min: number;
@@ -15,8 +15,8 @@ export interface PolicyEdgeInput {
15
15
  }
16
16
  export interface PolicyNodeBinder {
17
17
  (): CanonicalPolicyPatternTerm;
18
- (definition: Lazy<NodeClass>): CanonicalPolicyPatternTerm;
19
- readonly exact: (definition: Lazy<NodeClass>) => CanonicalPolicyPatternTerm;
18
+ (definition: NodeClassReferenceInput): CanonicalPolicyPatternTerm;
19
+ readonly exact: (definition: NodeClassReferenceInput) => CanonicalPolicyPatternTerm;
20
20
  }
21
21
  export interface PolicyExistsContext {
22
22
  readonly node: PolicyNodeBinder;
@@ -54,8 +54,8 @@ export declare function policyDeclaration(value: Policy): PolicyDeclaration;
54
54
  export declare const policy: PolicyBuilder;
55
55
  export declare function createPatternContext(options: {
56
56
  readonly resolveObject: (value: PolicyObjectInput, pointer: string) => PolicyObjectRef;
57
- readonly resolveEdgeClass: (value: Lazy<EdgeClass>, pointer: string) => ClassRef;
58
- readonly resolveNodeClass: (value: Lazy<NodeClass>, pointer: string) => ClassRef;
57
+ readonly resolveEdgeClass: (value: EdgeClassReferenceInput, pointer: string) => ClassRef;
58
+ readonly resolveNodeClass: (value: NodeClassReferenceInput, pointer: string) => ClassRef;
59
59
  readonly pointer: string;
60
60
  }): PolicyPatternContext;
61
61
  export type { PolicyCheckExpression };
@@ -71,7 +71,9 @@ export function createPatternContext(options) {
71
71
  if (args.length > 1 || (args.length === 1 && args[0] === undefined)) {
72
72
  fail('BUILDER_POLICY', `${options.pointer}/match/exists/nodes`, 'node(...) accepts either no arguments or one Node Class.');
73
73
  }
74
- return args.length === 0 ? bind('any') : bind('satisfies', args[0]);
74
+ return args.length === 0
75
+ ? bind('any')
76
+ : bind('satisfies', args[0]);
75
77
  };
76
78
  Object.defineProperty(node, 'exact', {
77
79
  enumerable: true,
@@ -5,6 +5,8 @@ import { DEPENDENCY_ALIAS_PATTERN, DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTER
5
5
  import { MEDIA_TYPE_PATTERN } from '../../language/class/model/data.js';
6
6
  import { PropertyValues } from '../../language/class/property-values.js';
7
7
  import { admitStateTopology } from '../../language/state/topology.js';
8
+ import { bindValueProgram } from '../../language/value/compile/load.js';
9
+ import { isValueObject } from '../../language/value/model/object.js';
8
10
  import { classifyValueProgramOrdering } from '../../language/value/ordering.js';
9
11
  import { invalidCompiled } from '../diagnostic.js';
10
12
  import { revisionOfCompiledEntry } from './revision.js';
@@ -19,10 +21,11 @@ export function acceptCompiledSchema(input) {
19
21
  exact(root, ['format', 'version', 'root', 'closure'], 'CompiledSchema', '');
20
22
  if (root.format !== 'astrale.dsl.compiled-schema')
21
23
  invalid('format', undefined, '/format');
22
- if (root.version !== 1)
24
+ if (root.version !== 1 && root.version !== 2)
23
25
  invalid('version', undefined, '/version');
26
+ const version = root.version;
24
27
  const rootPin = dependency(root.root, 'root', '/root');
25
- const closure = array(root.closure, 'closure', '/closure').map((value, index) => entry(value, index, `/closure/${index}`));
28
+ const closure = array(root.closure, 'closure', '/closure').map((value, index) => entry(value, index, `/closure/${index}`, version));
26
29
  if (closure.length > dependencyLimits.closure) {
27
30
  invalid('dependency closure limit', 'COMPILED_DEPENDENCY_LIMITS', '/closure');
28
31
  }
@@ -64,7 +67,7 @@ export function acceptCompiledSchema(input) {
64
67
  }
65
68
  const candidate = {
66
69
  format: 'astrale.dsl.compiled-schema',
67
- version: 1,
70
+ version: 2,
68
71
  root: rootPin,
69
72
  closure,
70
73
  };
@@ -72,9 +75,17 @@ export function acceptCompiledSchema(input) {
72
75
  validateCompiledSemantics(candidate);
73
76
  return deepFreeze(candidate);
74
77
  }
75
- function entry(input, entryIndex, path) {
78
+ function entry(input, entryIndex, path, version) {
76
79
  const value = object(input, `closure[${entryIndex}]`, path);
77
- exact(value, ['origin', 'revision', 'dependencies', 'definitions', 'facts', 'values'], 'entry', path);
80
+ exact(value, [
81
+ 'origin',
82
+ 'revision',
83
+ 'dependencies',
84
+ 'definitions',
85
+ 'facts',
86
+ 'values',
87
+ ...(version === 2 ? ['patterns'] : []),
88
+ ], 'entry', path);
78
89
  const origin = originValue(value.origin, 'entry origin', `${path}/origin`);
79
90
  const revision = revisionValue(value.revision, 'entry revision', `${path}/revision`);
80
91
  const dependenciesPath = `${path}/dependencies`;
@@ -86,14 +97,19 @@ function entry(input, entryIndex, path) {
86
97
  dependencyOrigins.add(dependency.origin);
87
98
  }
88
99
  const valuesPath = `${path}/values`;
89
- const values = array(value.values, 'values', valuesPath).map((item, index) => program(item, index, `${valuesPath}/${index}`));
100
+ const patternsPath = `${path}/patterns`;
101
+ const patterns = version === 2
102
+ ? array(value.patterns, 'entry patterns', patternsPath).map((item, index) => pattern(item, index, `${patternsPath}/${index}`, true))
103
+ : [];
104
+ const values = array(value.values, 'values', valuesPath).map((item, index) => program(item, index, `${valuesPath}/${index}`, patterns, version));
105
+ const bound = values.map((value) => bindValueProgram(value, patterns));
90
106
  const definitionsPath = `${path}/definitions`;
91
107
  const definitions = object(value.definitions, 'definitions', definitionsPath);
92
108
  exact(definitions, ['classes', 'functions', 'policies', 'views', 'core'], 'definitions', definitionsPath);
93
109
  const classesPath = `${definitionsPath}/classes`;
94
- const classes = array(definitions.classes, 'classes', classesPath).map((item, index) => compiledClass(item, values, `${classesPath}/${index}`));
110
+ const classes = array(definitions.classes, 'classes', classesPath).map((item, index) => compiledClass(item, bound, `${classesPath}/${index}`));
95
111
  const functionsPath = `${definitionsPath}/functions`;
96
- const functions = array(definitions.functions, 'functions', functionsPath).map((item, index) => compiledFunction(item, values, `${functionsPath}/${index}`));
112
+ const functions = array(definitions.functions, 'functions', functionsPath).map((item, index) => compiledFunction(item, bound, `${functionsPath}/${index}`));
97
113
  const factsPath = `${path}/facts`;
98
114
  const facts = object(value.facts, 'facts', factsPath);
99
115
  exact(facts, ['core'], 'facts', factsPath);
@@ -116,6 +132,7 @@ function entry(input, entryIndex, path) {
116
132
  core: array(facts.core, 'core facts', coreFactsPath).map((item, index) => compiledCoreEdge(item, `${coreFactsPath}/${index}`)),
117
133
  },
118
134
  values,
135
+ patterns,
119
136
  };
120
137
  const collections = [
121
138
  ['classes', accepted.definitions.classes],
@@ -554,18 +571,20 @@ function coreEndpoint(input, label, path) {
554
571
  if (value.kind === 'domain')
555
572
  exact(value, ['kind'], label, path);
556
573
  else
557
- definitionRef(value, 'core', label, path);
574
+ projectedMemberRef(value, label, path);
558
575
  }
559
576
  function policyObjectRef(input, path) {
560
- const ref = object(input, 'Policy object ref', path);
577
+ projectedMemberRef(object(input, 'Policy object ref', path), 'Policy object ref', path);
578
+ }
579
+ function projectedMemberRef(ref, label, path) {
561
580
  if (ref.kind !== 'method') {
562
- definitionRef(input, undefined, 'Policy object ref', path);
581
+ definitionRef(ref, undefined, label, path);
563
582
  return;
564
583
  }
565
- exact(ref, ['kind', 'owner', 'name'], 'Policy Method ref', path);
566
- definitionRef(ref.owner, 'class', 'Policy Method owner', `${path}/owner`);
584
+ exact(ref, ['kind', 'owner', 'name'], `${label} Method`, path);
585
+ definitionRef(ref.owner, 'class', `${label} Method owner`, `${path}/owner`);
567
586
  if (typeof ref.name !== 'string' || !MEMBER_NAME_PATTERN.test(ref.name))
568
- invalid('Policy Method name', undefined, `${path}/name`);
587
+ invalid(`${label} Method name`, undefined, `${path}/name`);
569
588
  }
570
589
  function definitionRef(input, kind, label, path) {
571
590
  if (!isDefinitionRef(input) || (kind !== undefined && input.kind !== kind))
@@ -752,16 +771,35 @@ function walk(input, path, visit) {
752
771
  for (const [key, value] of Object.entries(input))
753
772
  walk(value, pointer(path, key), visit);
754
773
  }
755
- function program(input, programIndex, path) {
774
+ function program(input, programIndex, path, table, version) {
756
775
  const value = object(input, `values[${programIndex}]`, path);
757
776
  exact(value, ['root', 'nodes', 'patterns'], 'ValueProgram', path);
758
777
  const nodesPath = `${path}/nodes`;
759
778
  const nodes = array(value.nodes, 'ValueProgram nodes', nodesPath).map((node, index) => valueNode(node, index, `${nodesPath}/${index}`));
760
779
  const root = index(value.root, nodes.length, 'ValueProgram root', `${path}/root`);
761
780
  const patternsPath = `${path}/patterns`;
762
- const patterns = array(value.patterns, 'ValueProgram patterns', patternsPath).map((item, index) => pattern(item, index, `${patternsPath}/${index}`));
763
- unique(patterns.map(({ source }) => source), 'patterns', patternsPath);
781
+ const patterns = array(value.patterns, 'ValueProgram patterns', patternsPath).map((item, position) => {
782
+ const patternPath = `${patternsPath}/${position}`;
783
+ if (version === 2)
784
+ return index(item, table.length, 'ValueProgram pattern', patternPath);
785
+ // Legacy programs can carry different machines with the same source label. Preserve each.
786
+ return table.push(pattern(item, position, patternPath, false)) - 1;
787
+ });
788
+ const sources = patterns.map((index) => table[index].source);
789
+ unique(sources, 'patterns', patternsPath);
790
+ const available = new Set(sources);
764
791
  for (const [nodeIndex, node] of nodes.entries()) {
792
+ if (isValueObject(node.schema)) {
793
+ const required = [node.schema.pattern, node.schema.fullMatch];
794
+ if (isValueObject(node.schema.patternProperties)) {
795
+ required.push(...Object.keys(node.schema.patternProperties));
796
+ }
797
+ for (const source of required) {
798
+ if (typeof source === 'string' && !available.has(source)) {
799
+ invalid('ValueProgram pattern binding', undefined, patternsPath);
800
+ }
801
+ }
802
+ }
765
803
  for (const [name, child] of Object.entries(node.children))
766
804
  index(child, nodes.length, 'ValueNode child', pointer(`${nodesPath}/${nodeIndex}/children`, name));
767
805
  if (node.reference !== undefined)
@@ -785,8 +823,10 @@ function valueNode(input, nodeIndex, path) {
785
823
  : { reference: integer(value.reference, 'reference', `${path}/reference`) }),
786
824
  };
787
825
  }
788
- function pattern(input, patternIndex, path) {
826
+ function pattern(input, patternIndex, path, counted) {
789
827
  const value = object(input, `patterns[${patternIndex}]`, path);
828
+ if (counted && Object.hasOwn(value, 'runs'))
829
+ return runPattern(value, path);
790
830
  exact(value, ['source', 'start', 'states'], 'Pattern', path);
791
831
  if (typeof value.source !== 'string')
792
832
  invalid('Pattern source', undefined, `${path}/source`);
@@ -831,6 +871,45 @@ function pattern(input, patternIndex, path) {
831
871
  }
832
872
  return { source: value.source, start, states };
833
873
  }
874
+ function runPattern(value, path) {
875
+ exact(value, ['source', 'runs'], 'RunPattern', path);
876
+ if (typeof value.source !== 'string')
877
+ invalid('Pattern source', undefined, `${path}/source`);
878
+ const entries = array(value.runs, 'scalar runs', `${path}/runs`);
879
+ if (entries.length === 0 || entries.length > 4096) {
880
+ invalid('scalar run count', undefined, `${path}/runs`);
881
+ }
882
+ const runs = entries.map((entry, position) => {
883
+ const runPath = `${path}/runs/${position}`;
884
+ const run = object(entry, 'scalar run', runPath);
885
+ exact(run, ['ranges', 'minimum'], 'scalar run', runPath, ['maximum']);
886
+ const minimum = boundedInteger(run.minimum, 0, 1000, 'run minimum', `${runPath}/minimum`);
887
+ const maximum = run.maximum === undefined
888
+ ? undefined
889
+ : boundedInteger(run.maximum, minimum, 1000, 'run maximum', `${runPath}/maximum`);
890
+ if (position !== entries.length - 1 && minimum !== maximum) {
891
+ invalid('exact scalar prefix', undefined, `${runPath}/maximum`);
892
+ }
893
+ let previous = -1;
894
+ const ranges = array(run.ranges, 'scalar ranges', `${runPath}/ranges`).map((entry, index) => {
895
+ const rangePath = `${runPath}/ranges/${index}`;
896
+ const range = array(entry, 'scalar range', rangePath);
897
+ if (range.length !== 2)
898
+ invalid('scalar range', undefined, rangePath);
899
+ const minimum = scalar(range[0], 'range minimum', `${rangePath}/0`);
900
+ const maximum = scalar(range[1], 'range maximum', `${rangePath}/1`);
901
+ if (minimum > maximum ||
902
+ minimum <= previous ||
903
+ (minimum >= 0xd800 && minimum <= 0xdfff) ||
904
+ (maximum >= 0xd800 && maximum <= 0xdfff))
905
+ invalid('ordered scalar range', undefined, rangePath);
906
+ previous = maximum;
907
+ return [minimum, maximum];
908
+ });
909
+ return { ranges, minimum, ...(maximum === undefined ? {} : { maximum }) };
910
+ });
911
+ return { source: value.source, runs };
912
+ }
834
913
  function dependency(input, label, path) {
835
914
  const value = object(input, label, path);
836
915
  exact(value, ['origin', 'revision'], label, path);
@@ -3,10 +3,11 @@ import { Key, PropertyKey as PropertyKeyValue } from '../../addressing/index.js'
3
3
  import { compareUnicode } from '../../addressing/ordering.js';
4
4
  import { composeMethods } from '../../language/callable/composition.js';
5
5
  import { isMethodExecutable } from '../../language/callable/executable.js';
6
- import { policyObjectOrigin, policyVariableClass } from '../../language/index.js';
6
+ import { policyObjectKey, policyObjectOrigin, policyVariableClass, projectedMemberClass, } from '../../language/index.js';
7
7
  import { branchesOf, branchProduct, capBranches, isConnected, targetMode, termKey, } from '../../language/policy/analysis/branches.js';
8
8
  import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, } from '../../language/policy/analysis/budgets.js';
9
9
  import { normalizePolicyExpression } from '../../language/policy/normalization/normalize.js';
10
+ import { bindValueProgram } from '../../language/value/compile/load.js';
10
11
  import { validateValueProgram } from '../../language/value/evaluate/program.js';
11
12
  import { GRAPH_NODE_PATH_SCHEMA_ID } from '../../language/value/syntax/node-path/schema.js';
12
13
  import { invalidCompiled } from '../diagnostic.js';
@@ -140,7 +141,7 @@ function outputKey(entry, output) {
140
141
  return output.mode === 'binary' ? 'binary' : `${output.mode}:${programKey(entry, output.value)}`;
141
142
  }
142
143
  function programKey(entry, value) {
143
- return json.serialize(entry.values[value]);
144
+ return json.serialize(bindValueProgram(entry.values[value], entry.patterns));
144
145
  }
145
146
  function validateClassFacets(context) {
146
147
  for (const located of context.classes.values()) {
@@ -984,8 +985,8 @@ function validateCore(candidate, context) {
984
985
  if (analysis === undefined)
985
986
  fail(`${path}/class`, 'Core Edge Class analysis is absent.');
986
987
  validateCoreProperties(value.properties, analysis, `${path}/properties`);
987
- const source = resolveCoreEndpoint(value.source, nodes, `${path}/source`);
988
- const target = resolveCoreEndpoint(value.target, nodes, `${path}/target`);
988
+ const source = resolveCoreEndpoint(context, entry, value.source, nodes, `${path}/source`);
989
+ const target = resolveCoreEndpoint(context, entry, value.target, nodes, `${path}/target`);
989
990
  const declaration = classValue.value;
990
991
  const satisfies = declaration.orientation === 'directed'
991
992
  ? endpointSatisfies(context, source, declaration.endpoints.source.accepts) &&
@@ -1019,21 +1020,44 @@ function validateCoreProperties(values, analysis, path) {
1019
1020
  }
1020
1021
  if (property.declaration.auto !== undefined)
1021
1022
  fail(`${path}/${escapePointer(property.key)}`, 'Core properties cannot author an automatic Property.');
1022
- if (validateValueProgram(property.entry.values[property.declaration.value], values[property.key])
1023
- .length > 0)
1023
+ if (validateValueProgram(bindValueProgram(property.entry.values[property.declaration.value], property.entry.patterns), values[property.key]).length > 0)
1024
1024
  fail(`${path}/${escapePointer(property.key)}`, 'Core Property value does not satisfy its exact contract.');
1025
1025
  }
1026
1026
  }
1027
- function resolveCoreEndpoint(ref, nodes, path) {
1027
+ function resolveCoreEndpoint(context, entry, ref, nodes, path) {
1028
1028
  if (ref.kind === 'domain')
1029
1029
  return { key: 'domain' };
1030
- const node = nodes.get(Key.of(ref));
1031
- if (node === undefined)
1032
- fail(path, 'Core endpoint does not name a declared local Core Node.');
1033
- return node;
1030
+ const undeclared = () => fail(path, 'Core endpoint does not name a declared local Definition.');
1031
+ if (policyObjectOrigin(ref) !== entry.origin)
1032
+ return undeclared();
1033
+ if (ref.kind === 'core')
1034
+ return nodes.get(Key.of(ref)) ?? undeclared();
1035
+ if (!declaresProjectedMember(context, entry, ref))
1036
+ return undeclared();
1037
+ return { key: policyObjectKey(ref), class: projectedMemberClass(ref), pointer: path };
1038
+ }
1039
+ /** Only a projected declaration can carry a Core fact; an abstract Method is never projected. */
1040
+ function declaresProjectedMember(context, entry, ref) {
1041
+ if (ref.kind === 'method') {
1042
+ const owner = context.classes.get(Key.of(ref.owner))?.value;
1043
+ const method = owner?.kind === 'node' ? owner.methods.find((value) => value.name === ref.name) : undefined;
1044
+ return method !== undefined && isMethodExecutable(owner, method);
1045
+ }
1046
+ const declarations = ref.kind === 'class'
1047
+ ? entry.definitions.classes
1048
+ : ref.kind === 'function'
1049
+ ? entry.definitions.functions
1050
+ : ref.kind === 'policy'
1051
+ ? entry.definitions.policies
1052
+ : entry.definitions.views;
1053
+ return declarations.some((declaration) => declaration.ref.name === ref.name);
1034
1054
  }
1035
1055
  function endpointSatisfies(context, endpoint, accepts) {
1036
- return !('value' in endpoint) || classSatisfiesAny(context, endpoint.value.class, accepts);
1056
+ if ('value' in endpoint)
1057
+ return classSatisfiesAny(context, endpoint.value.class, accepts);
1058
+ if ('class' in endpoint)
1059
+ return classSatisfiesAny(context, endpoint.class, accepts);
1060
+ return true;
1037
1061
  }
1038
1062
  function validateCoreEdgeSet(context, entry, entryIndex, nodes, edges) {
1039
1063
  const seen = new Set();
@@ -1064,27 +1088,56 @@ function validateCoreEdgeSet(context, entry, entryIndex, nodes, edges) {
1064
1088
  }
1065
1089
  }
1066
1090
  function validateCardinality(context, declaration, edges, nodes) {
1067
- for (const node of nodes.values()) {
1091
+ for (const subject of cardinalitySubjects(edges, nodes)) {
1068
1092
  if (declaration.orientation === 'directed') {
1069
- if (classSatisfiesAny(context, node.value.class, declaration.endpoints.source.accepts))
1070
- checkCount(declaration.endpoints.source.outgoing, edges.filter(({ source }) => source.key === node.key).length, node.pointer);
1071
- if (classSatisfiesAny(context, node.value.class, declaration.endpoints.target.accepts))
1072
- checkCount(declaration.endpoints.target.incoming, edges.filter(({ target }) => target.key === node.key).length, node.pointer);
1093
+ if (classSatisfiesAny(context, subject.class, declaration.endpoints.source.accepts))
1094
+ checkCount(declaration.endpoints.source.outgoing, edges.filter(({ source }) => source.key === subject.key).length, subject.pointer, subject.minimum);
1095
+ if (classSatisfiesAny(context, subject.class, declaration.endpoints.target.accepts))
1096
+ checkCount(declaration.endpoints.target.incoming, edges.filter(({ target }) => target.key === subject.key).length, subject.pointer, subject.minimum);
1073
1097
  }
1074
1098
  else {
1075
- const degree = edges.filter(({ source, target }) => source.key === node.key || target.key === node.key).length;
1099
+ const degree = edges.filter(({ source, target }) => source.key === subject.key || target.key === subject.key).length;
1076
1100
  const [first, second] = declaration.endpoints;
1077
- if (classSatisfiesAny(context, node.value.class, first.accepts))
1078
- checkCount(first.incident, degree, node.pointer);
1101
+ if (classSatisfiesAny(context, subject.class, first.accepts))
1102
+ checkCount(first.incident, degree, subject.pointer, subject.minimum);
1079
1103
  if (!sameIncidentEndpoint(first, second) &&
1080
- classSatisfiesAny(context, node.value.class, second.accepts))
1081
- checkCount(second.incident, degree, node.pointer);
1104
+ classSatisfiesAny(context, subject.class, second.accepts))
1105
+ checkCount(second.incident, degree, subject.pointer, subject.minimum);
1106
+ }
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Every declared Core Node proves its own complete degree, so both bounds hold. A Definition
1111
+ * endpoint is only named by the Edges that reach it: the Domain may also create ordinary Edges of
1112
+ * the same Class against it, so a minimum is unprovable here and only the maximum is enforced.
1113
+ */
1114
+ function cardinalitySubjects(edges, nodes) {
1115
+ const subjects = new Map();
1116
+ for (const node of nodes.values()) {
1117
+ subjects.set(node.key, {
1118
+ key: node.key,
1119
+ class: node.value.class,
1120
+ pointer: node.pointer,
1121
+ minimum: true,
1122
+ });
1123
+ }
1124
+ for (const edge of edges) {
1125
+ for (const endpoint of [edge.source, edge.target]) {
1126
+ if ('value' in endpoint || !('class' in endpoint) || subjects.has(endpoint.key))
1127
+ continue;
1128
+ subjects.set(endpoint.key, {
1129
+ key: endpoint.key,
1130
+ class: endpoint.class,
1131
+ pointer: endpoint.pointer,
1132
+ minimum: false,
1133
+ });
1082
1134
  }
1083
1135
  }
1136
+ return Object.freeze([...subjects.values()]);
1084
1137
  }
1085
- function checkCount(count, degree, path) {
1138
+ function checkCount(count, degree, path, enforceMinimum) {
1086
1139
  const [minimum, maximum] = countBounds(count);
1087
- if (degree < minimum || degree > maximum)
1140
+ if (degree > maximum || (degree < minimum && enforceMinimum))
1088
1141
  fail(path, `Core degree ${degree} violates Edge count ${count}.`);
1089
1142
  }
1090
1143
  function countBounds(value) {
@@ -1,6 +1,6 @@
1
1
  import { inputFieldNodes } from '../../language/value/compile/fields.js';
2
2
  import { prepareValueSchema } from '../../language/value/evaluate/preparation.js';
3
- import { classifyOrdering } from '../../language/value/ordering.js';
3
+ import { classifyPreparedOrdering } from '../../language/value/ordering.js';
4
4
  import { revisionOfDomainSchema } from '../../schema/canonical/revision.js';
5
5
  import { lookupForDomainClosure, requireDomainClosure } from '../../schema/resolution/closure.js';
6
6
  import { createDomainValueSchemaRegistry } from '../../schema/resolution/value.registry.js';
@@ -16,7 +16,7 @@ export function compile(schema) {
16
16
  const closure = Object.freeze(sources.map((source) => compileEntry(source, lookup)));
17
17
  return Object.freeze({
18
18
  format: 'astrale.dsl.compiled-schema',
19
- version: 1,
19
+ version: 2,
20
20
  root: Object.freeze({ origin: schema.origin, revision: revisionOfDomainSchema(schema) }),
21
21
  closure,
22
22
  });
@@ -24,16 +24,31 @@ export function compile(schema) {
24
24
  function compileEntry(schema, lookup) {
25
25
  const registry = createDomainValueSchemaRegistry(schema, lookup);
26
26
  const values = [];
27
- const add = (contract, pointer) => {
28
- const result = prepareValueSchema(contract, registry, pointer);
27
+ const pool = new Map();
28
+ const patternIndices = new Map();
29
+ const prepare = (contract, pointer) => {
30
+ const result = prepareValueSchema(contract, registry, pointer, pool);
29
31
  if (!result.compiled)
30
32
  throw new TypeError(`Cannot compile ValueSchema: ${result.message}`);
31
- return values.push(result.value.program()) - 1;
33
+ return result.value;
32
34
  };
35
+ const addProgram = (program) => {
36
+ const patterns = program.patterns.map((pattern) => {
37
+ let index = patternIndices.get(pattern);
38
+ if (index === undefined) {
39
+ index = patternIndices.size;
40
+ patternIndices.set(pattern, index);
41
+ }
42
+ return index;
43
+ });
44
+ return values.push(Object.freeze({ ...program, patterns: Object.freeze(patterns) })) - 1;
45
+ };
46
+ const add = (contract, pointer) => addProgram(prepare(contract, pointer).program());
33
47
  const properties = (definition, pointer) => Object.entries(definition.properties)
34
48
  .sort(([left], [right]) => compare(left, right))
35
49
  .map(([name, property]) => {
36
- const ordering = classifyOrdering(property.schema, registry);
50
+ const prepared = prepare(property.schema, `${pointer}/properties/${name}/schema`);
51
+ const ordering = classifyPreparedOrdering(prepared);
37
52
  return {
38
53
  name,
39
54
  required: property.required,
@@ -41,7 +56,7 @@ function compileEntry(schema, lookup) {
41
56
  ...(property.description === undefined ? {} : { description: property.description }),
42
57
  ...(property.state === undefined ? {} : { state: property.state }),
43
58
  ...(ordering === undefined ? {} : { ordering }),
44
- value: add(property.schema, `${pointer}/properties/${name}/schema`),
59
+ value: addProgram(prepared.program()),
45
60
  };
46
61
  });
47
62
  const output = (value, pointer) => value.mode === 'binary'
@@ -106,6 +121,7 @@ function compileEntry(schema, lookup) {
106
121
  }),
107
122
  facts: Object.freeze({ core: schema.core.edges }),
108
123
  values: Object.freeze(values),
124
+ patterns: Object.freeze([...patternIndices.keys()]),
109
125
  });
110
126
  }
111
127
  function compare(left, right) {
@@ -1,4 +1,12 @@
1
- import type { CompiledSchema, DomainOfCompiled } from '../model/schema.js';
1
+ import type { ValueProgram } from '../../language/value/compile/model.js';
2
+ import type { CompiledSchema, CompiledSchemaEntry, DomainOfCompiled } from '../model/schema.js';
3
+ type LegacyEntry = Omit<CompiledSchemaEntry, 'values' | 'patterns'> & {
4
+ readonly values: readonly ValueProgram[];
5
+ };
6
+ type LegacyCompiledSchema = Omit<CompiledSchema, 'version' | 'closure'> & {
7
+ readonly version: 1;
8
+ readonly closure: readonly LegacyEntry[];
9
+ };
2
10
  /** Load admitted compiled data through the one resolved Domain constructor. */
3
11
  export declare function load<const Input extends CompiledSchema>(input: Input): DomainOfCompiled<Input>;
4
12
  export declare function load(input: unknown): import('../../domain/model/domain.js').Domain;
@@ -6,4 +14,5 @@ export declare function load(input: unknown): import('../../domain/model/domain.
6
14
  * Realize compiled material whose exact embedded bytes were already proven by generated build code.
7
15
  * External bytes and caller-provided values must continue through {@link load}.
8
16
  */
9
- export declare function realize(compiled: CompiledSchema): import('../../domain/model/domain.js').Domain;
17
+ export declare function realize(compiled: CompiledSchema | LegacyCompiledSchema): import('../../domain/model/domain.js').Domain;
18
+ export {};