@astrale-os/kernel-dsl 0.2.0-beta.45 → 0.2.0-beta.47

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 (76) hide show
  1. package/dist/v1/addressing/class-key.js +5 -4
  2. package/dist/v1/addressing/method-key.js +3 -2
  3. package/dist/v1/builder/class/property.d.ts +3 -1
  4. package/dist/v1/builder/class/property.js +9 -1
  5. package/dist/v1/builder/lowering/schema/compile.js +1 -1
  6. package/dist/v1/builder/lowering/value/compile.js +3 -0
  7. package/dist/v1/builder/lowering/value/zod.js +11 -2
  8. package/dist/v1/builder/lowering/view.d.ts +2 -2
  9. package/dist/v1/builder/lowering/view.js +2 -14
  10. package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
  11. package/dist/v1/builder/value/full-match.js +6 -3
  12. package/dist/v1/builder/view/authoring.d.ts +3 -7
  13. package/dist/v1/builder/view/authoring.js +5 -2
  14. package/dist/v1/compiled/admission/accept.js +31 -6
  15. package/dist/v1/compiled/admission/semantics.js +49 -16
  16. package/dist/v1/compiled/compiler/compile.js +1 -0
  17. package/dist/v1/compiled/loader/load.js +1 -0
  18. package/dist/v1/domain/create.js +0 -19
  19. package/dist/v1/domain/model/domain.d.ts +0 -1
  20. package/dist/v1/domain/model/input.d.ts +3 -0
  21. package/dist/v1/domain/projection/classes.js +1 -0
  22. package/dist/v1/domain/validation/create.d.ts +2 -0
  23. package/dist/v1/domain/validation/create.js +12 -0
  24. package/dist/v1/language/class/model/property.d.ts +3 -0
  25. package/dist/v1/language/class/resolved.d.ts +3 -0
  26. package/dist/v1/language/index.d.ts +2 -0
  27. package/dist/v1/language/index.js +1 -0
  28. package/dist/v1/language/policy/normalization/normalize.d.ts +4 -0
  29. package/dist/v1/language/policy/normalization/normalize.js +51 -13
  30. package/dist/v1/language/value/inclusion/includes.d.ts +9 -0
  31. package/dist/v1/language/value/inclusion/includes.js +777 -0
  32. package/dist/v1/language/value/profile/rules.js +11 -32
  33. package/dist/v1/language/value/regexp/ast.js +3 -64
  34. package/dist/v1/language/value/regexp/compile.d.ts +25 -2
  35. package/dist/v1/language/value/regexp/compile.js +48 -12
  36. package/dist/v1/language/value/regexp/syntax.d.ts +11 -1
  37. package/dist/v1/language/value/regexp/syntax.js +14 -2
  38. package/dist/v1/language/value/regexp/unicode.d.ts +32 -0
  39. package/dist/v1/language/value/regexp/unicode.js +139 -0
  40. package/dist/v1/language/value/syntax/full-match.js +6 -3
  41. package/dist/v1/language/view/model/view.d.ts +6 -0
  42. package/dist/v1/language/view/resolved.d.ts +2 -2
  43. package/dist/v1/schema/analysis/class.d.ts +2 -1
  44. package/dist/v1/schema/analysis/class.js +1 -0
  45. package/dist/v1/schema/compatibility/comparators/callables.d.ts +7 -0
  46. package/dist/v1/schema/compatibility/comparators/callables.js +13 -0
  47. package/dist/v1/schema/compatibility/comparators/classes.d.ts +18 -0
  48. package/dist/v1/schema/compatibility/comparators/classes.js +54 -0
  49. package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
  50. package/dist/v1/schema/compatibility/comparators/index.js +14 -2
  51. package/dist/v1/schema/compatibility/context.d.ts +6 -2
  52. package/dist/v1/schema/compatibility/context.js +12 -6
  53. package/dist/v1/schema/compatibility/evidence.d.ts +8 -1
  54. package/dist/v1/schema/compatibility/evidence.js +10 -1
  55. package/dist/v1/schema/compatibility/fields.d.ts +2 -0
  56. package/dist/v1/schema/compatibility/fields.js +1 -0
  57. package/dist/v1/schema/compatibility/meaning/footprint.js +3 -1
  58. package/dist/v1/schema/compatibility/meaning/index.js +4 -2
  59. package/dist/v1/schema/compatibility/model.d.ts +11 -6
  60. package/dist/v1/schema/compatibility/references.d.ts +8 -1
  61. package/dist/v1/schema/compatibility/references.js +39 -6
  62. package/dist/v1/schema/compatibility/scope.d.ts +5 -0
  63. package/dist/v1/schema/compatibility/scope.js +5 -2
  64. package/dist/v1/schema/compatibility/structure/index.js +21 -2
  65. package/dist/v1/schema/compatibility/structure/requirements.js +26 -1
  66. package/dist/v1/schema/compatibility/subjects.js +20 -2
  67. package/dist/v1/schema/diagnostic.d.ts +1 -1
  68. package/dist/v1/schema/normalization/domain.js +1 -0
  69. package/dist/v1/schema/resolution/domain.d.ts +4 -1
  70. package/dist/v1/schema/resolution/domain.js +7 -7
  71. package/dist/v1/schema/resolution/origins.d.ts +11 -4
  72. package/dist/v1/schema/resolution/origins.js +7 -8
  73. package/dist/v1/schema/validation/class.d.ts +10 -0
  74. package/dist/v1/schema/validation/class.js +13 -2
  75. package/dist/v1/schema/validation/domain.structure.js +1 -0
  76. package/package.json +1 -1
@@ -20,10 +20,11 @@ export function ClassKey(input) {
20
20
  function is(input) {
21
21
  if (typeof input !== 'string' || !Key.is(input))
22
22
  return false;
23
- const separator = input.indexOf(':');
24
- return (input.slice(separator + 1).startsWith('class.') &&
25
- !input.includes('.property.') &&
26
- !input.includes('.method.'));
23
+ // Only the coordinate is tested: an origin label may itself be `property` or `method`.
24
+ const coordinate = input.slice(input.indexOf(':') + 1);
25
+ return (coordinate.startsWith('class.') &&
26
+ !coordinate.includes('.property.') &&
27
+ !coordinate.includes('.method.'));
27
28
  }
28
29
  ClassKey.is = is;
29
30
  function ref(input) {
@@ -21,8 +21,9 @@ export function MethodKey(input) {
21
21
  function is(input) {
22
22
  if (typeof input !== 'string' || !Key.is(input))
23
23
  return false;
24
- const coordinate = input.slice(input.indexOf(':') + 1);
25
- return coordinate.startsWith('class.') && coordinate.includes('.method.');
24
+ // The member kind is the third segment: the owner Class may itself be named `method`.
25
+ const [kind, , member] = input.slice(input.indexOf(':') + 1).split('.');
26
+ return kind === 'class' && member === 'method';
26
27
  }
27
28
  MethodKey.is = is;
28
29
  function address(input) {
@@ -1,5 +1,5 @@
1
1
  import type { z } from 'zod';
2
- import type { AutoEvent, AutoSet } from '../../language/class/model/property.js';
2
+ import type { AutoEvent, AutoSet, PropertyImmutability } from '../../language/class/model/property.js';
3
3
  import type { StateMachine } from '../../language/state/index.js';
4
4
  import type { JsonSchema } from '../../language/value/model/schema.js';
5
5
  import type { AUTHORING } from '../schema/handle.js';
@@ -9,6 +9,8 @@ export type ValueSchemaInput = z.ZodType | JsonSchema;
9
9
  export interface PropertyOptions {
10
10
  readonly required?: boolean;
11
11
  readonly description?: string;
12
+ /** `create` freezes the value at creation; `once` admits one write while absent. */
13
+ readonly immutable?: PropertyImmutability;
12
14
  }
13
15
  export interface AutoOptions {
14
16
  readonly on: AutoEvent | readonly AutoEvent[];
@@ -10,7 +10,12 @@ export function autoPropertyAuthoringInput(value) {
10
10
  }
11
11
  export function property(schema, options) {
12
12
  const admitted = options ?? {};
13
- assertKnownKeys(admitted, ['required', 'description']);
13
+ assertKnownKeys(admitted, ['required', 'description', 'immutable']);
14
+ if (admitted.immutable !== undefined &&
15
+ admitted.immutable !== 'create' &&
16
+ admitted.immutable !== 'once') {
17
+ fail('BUILDER_PROPERTY_IMMUTABLE_INVALID', '/immutable', 'immutable is create or once.');
18
+ }
14
19
  return authoringValue('property', {
15
20
  [PROPERTY_INPUT]: {
16
21
  schema,
@@ -34,6 +39,9 @@ export function auto(property, options) {
34
39
  fail('BUILDER_AUTO_NESTED', '', 'auto(...) cannot wrap another auto Property.');
35
40
  }
36
41
  assertKnownKeys(options, ['on', 'set']);
42
+ if (propertyAuthoringInput(property).options.immutable !== undefined) {
43
+ fail('BUILDER_AUTO_IMMUTABLE', '', 'An auto Property cannot be immutable.');
44
+ }
37
45
  const authored = Array.isArray(options.on) ? options.on : [options.on];
38
46
  if (authored.length === 0)
39
47
  fail('BUILDER_AUTO_EVENT_INVALID', '/on', 'auto(...) requires an event.');
@@ -47,7 +47,7 @@ function assembleDomainSchema(origin, input, lookup) {
47
47
  }));
48
48
  const policies = mapRecord(input.policies ?? {}, (value, name) => policyCompiler.compile(value, name, `/policies/${escapeToken(name)}`));
49
49
  assertPolicyCompositionAcyclic(policies, origin);
50
- const views = mapRecord(input.views ?? {}, (value, name) => viewCompiler.compile(value, name, `/views/${escapeToken(name)}`));
50
+ const views = mapRecord(input.views ?? {}, (value, name) => viewCompiler.compile(value, name));
51
51
  const core = compileCore(input.core, context);
52
52
  const semanticContent = {
53
53
  format: 'astrale.dsl',
@@ -37,6 +37,9 @@ export class BuilderValueSchemas {
37
37
  schema: canonicalizeValueSchema(schema, 'property-facet', this.registry),
38
38
  required: authoredInput?.options.required ?? isRequired(schemaInput),
39
39
  ...(autoInput === undefined ? {} : { auto: autoInput.options }),
40
+ ...(authoredInput?.options.immutable === undefined
41
+ ? {}
42
+ : { immutable: authoredInput.options.immutable }),
40
43
  ...(authoredInput?.options.description === undefined
41
44
  ? {}
42
45
  : { description: authoredInput.options.description }),
@@ -161,7 +161,10 @@ function inspectCheck(def, pointer, diagnostics) {
161
161
  return;
162
162
  }
163
163
  if (def.check === 'string_format' && def.format === 'regex' && def.pattern instanceof RegExp) {
164
- if (!isLosslessRegexp(def.pattern)) {
164
+ if (def.pattern.flags === '') {
165
+ diagnostics.push(diagnostic('ZOD_SCHEMA_NOT_LOSSLESS', pointer, 'Zod RegExp without the u flag reads \\p{...} as literal text, and classes and quantifiers by UTF-16 code unit, unlike the V1 I-Regexp it would lower to; add the u flag.'));
166
+ }
167
+ else if (!isLosslessRegexp(def.pattern)) {
165
168
  diagnostics.push(diagnostic('ZOD_SCHEMA_NOT_LOSSLESS', pointer, 'Zod RegExp flags, anchors, dot semantics, or syntax cannot be preserved as a V1 I-Regexp substring pattern.'));
166
169
  }
167
170
  }
@@ -292,8 +295,14 @@ function escapePointerToken(value) {
292
295
  function isPlainRecord(value) {
293
296
  return value !== null && typeof value === 'object' && !Array.isArray(value);
294
297
  }
298
+ /**
299
+ * Whether a Zod RegExp search means its source read as an I-Regexp. Only the u flag qualifies:
300
+ * without it, `\p{L}` matches the text `p{L}`, and classes and quantifiers apply to UTF-16 code
301
+ * units rather than scalars; `v` reads class syntax differently, and every other flag changes the
302
+ * match itself.
303
+ */
295
304
  function isLosslessRegexp(pattern) {
296
- if (pattern.flags !== '' && pattern.flags !== 'u')
305
+ if (pattern.flags !== 'u')
297
306
  return false;
298
307
  if (!isIRegexp(pattern.source))
299
308
  return false;
@@ -2,10 +2,10 @@ import type { ViewDefinition } from '../../language/view/model/view.js';
2
2
  import type { View } from '../view/authoring.js';
3
3
  import type { BuilderContext } from './schema/context.js';
4
4
  export interface ViewCompiler {
5
- compile(value: View, name: string, pointer: string): ViewDefinition;
5
+ compile(value: View, name: string): ViewDefinition;
6
6
  }
7
7
  export declare class V1ViewCompiler implements ViewCompiler {
8
8
  readonly context: BuilderContext;
9
9
  constructor(context: BuilderContext);
10
- compile(value: View, name: string, pointer: string): ViewDefinition;
10
+ compile(value: View, name: string): ViewDefinition;
11
11
  }
@@ -1,27 +1,15 @@
1
- import { compareDefinitionRefs } from '../../addressing/ordering.js';
2
1
  import { viewAuthoringInput } from '../view/authoring.js';
3
2
  export class V1ViewCompiler {
4
3
  context;
5
4
  constructor(context) {
6
5
  this.context = context;
7
6
  }
8
- compile(value, name, pointer) {
7
+ compile(value, name) {
9
8
  const config = viewAuthoringInput(value);
10
- const target = config.target;
11
9
  return {
12
10
  ref: { origin: this.context.origin, kind: 'view', name },
13
11
  ...(config.description === undefined ? {} : { description: config.description }),
14
- target: target === undefined || target === 'domain'
15
- ? { kind: 'domain' }
16
- : {
17
- kind: 'definition',
18
- definitions: (isTargetList(target) ? target : [target])
19
- .map((entry, index) => this.context.resolveNodeClass(entry, `${pointer}/target/definitions/${index}`))
20
- .sort(compareDefinitionRefs),
21
- },
12
+ target: { kind: 'domain' },
22
13
  };
23
14
  }
24
15
  }
25
- function isTargetList(input) {
26
- return Array.isArray(input);
27
- }
@@ -1,5 +1,5 @@
1
1
  import type { DiagnosticCode as LanguageDiagnosticCode } from '../../language/validation/diagnostic.js';
2
- export type BuilderDiagnosticCode = LanguageDiagnosticCode | 'BUILDER_ABSTRACT_POLICY' | 'BUILDER_AMBIGUOUS_OWNERSHIP' | 'BUILDER_AUTO_EVENT_INVALID' | 'BUILDER_AUTO_NESTED' | 'BUILDER_AUTO_SET_INVALID' | 'BUILDER_CALLABLE_AUTH' | 'BUILDER_CALLABLE_PLACEMENT' | 'BUILDER_CALLABLE_POLICY_AUTH' | 'BUILDER_CORE_CLASS' | 'BUILDER_CORE_EDGE' | 'BUILDER_CORE_NODE' | 'BUILDER_CORE_PROPERTY' | 'BUILDER_CORE_REF' | 'BUILDER_DEFINITION' | 'BUILDER_DEPENDENCY_CONFLICT' | 'BUILDER_DEPENDENCY_ALIAS' | 'BUILDER_DEPENDENCY_DIRECT' | 'BUILDER_DUPLICATE_MEMBER' | 'BUILDER_FOREIGN_POLICY' | 'BUILDER_FULL_MATCH_INVALID' | 'BUILDER_FUNCTION' | 'BUILDER_METHOD' | 'BUILDER_PATH_DUPLICATE' | 'BUILDER_PATH_EMPTY' | 'BUILDER_POLICY' | 'BUILDER_POLICY_COMPOSITION' | 'BUILDER_POLICY_CYCLE' | 'BUILDER_POLICY_EMPTY_COMPOSITION' | 'BUILDER_POLICY_EMPTY_EXISTS' | 'BUILDER_POLICY_INPUT' | 'BUILDER_POLICY_REF' | 'BUILDER_POLICY_SELF' | 'BUILDER_REFERENCE_CYCLE' | 'BUILDER_REFERENCE_KIND' | 'BUILDER_REFERENCE_MISSING' | 'BUILDER_UNKNOWN_FIELD' | 'BUILDER_UNOWNED_REFERENCE' | 'BUILDER_VIEW' | 'ZOD_SCHEMA_NOT_LOSSLESS';
2
+ export type BuilderDiagnosticCode = LanguageDiagnosticCode | 'BUILDER_ABSTRACT_POLICY' | 'BUILDER_AMBIGUOUS_OWNERSHIP' | 'BUILDER_AUTO_EVENT_INVALID' | 'BUILDER_AUTO_IMMUTABLE' | 'BUILDER_AUTO_NESTED' | 'BUILDER_AUTO_SET_INVALID' | 'BUILDER_CALLABLE_AUTH' | 'BUILDER_CALLABLE_PLACEMENT' | 'BUILDER_CALLABLE_POLICY_AUTH' | 'BUILDER_CORE_CLASS' | 'BUILDER_CORE_EDGE' | 'BUILDER_CORE_NODE' | 'BUILDER_CORE_PROPERTY' | 'BUILDER_CORE_REF' | 'BUILDER_DEFINITION' | 'BUILDER_DEPENDENCY_CONFLICT' | 'BUILDER_DEPENDENCY_ALIAS' | 'BUILDER_DEPENDENCY_DIRECT' | 'BUILDER_DUPLICATE_MEMBER' | 'BUILDER_FOREIGN_POLICY' | 'BUILDER_FULL_MATCH_INVALID' | 'BUILDER_FUNCTION' | 'BUILDER_METHOD' | 'BUILDER_PATH_DUPLICATE' | 'BUILDER_PATH_EMPTY' | 'BUILDER_POLICY' | 'BUILDER_POLICY_COMPOSITION' | 'BUILDER_POLICY_CYCLE' | 'BUILDER_POLICY_EMPTY_COMPOSITION' | 'BUILDER_POLICY_EMPTY_EXISTS' | 'BUILDER_POLICY_INPUT' | 'BUILDER_POLICY_REF' | 'BUILDER_POLICY_SELF' | 'BUILDER_PROPERTY_IMMUTABLE_INVALID' | 'BUILDER_REFERENCE_CYCLE' | 'BUILDER_REFERENCE_KIND' | 'BUILDER_REFERENCE_MISSING' | 'BUILDER_UNKNOWN_FIELD' | 'BUILDER_UNOWNED_REFERENCE' | 'BUILDER_VIEW' | 'ZOD_SCHEMA_NOT_LOSSLESS';
3
3
  export interface BuilderDiagnostic<out Code extends BuilderDiagnosticCode = BuilderDiagnosticCode> {
4
4
  readonly code: Code;
5
5
  readonly pointer: string;
@@ -1,11 +1,14 @@
1
- import { isIRegexp } from '../../language/value/regexp/syntax.js';
1
+ import { refuseIRegexp, stateBudgetMessage } from '../../language/value/regexp/syntax.js';
2
2
  import { fullMatch as languageFullMatch, } from '../../language/value/syntax/full-match.js';
3
3
  import { fail } from '../schema/diagnostic.js';
4
4
  export { fullMatchSource } from '../../language/value/syntax/full-match.js';
5
5
  /** Authoring facade preserving the Builder-owned diagnostic for invalid source syntax. */
6
6
  export function fullMatch(source) {
7
- if (!isIRegexp(source)) {
8
- fail('BUILDER_FULL_MATCH_INVALID', '/fullMatch', 'fullMatch(...) requires a complete RFC 9485 I-Regexp source.');
7
+ const refusal = refuseIRegexp(source);
8
+ if (refusal !== undefined) {
9
+ fail('BUILDER_FULL_MATCH_INVALID', '/fullMatch', refusal === 'syntax'
10
+ ? 'fullMatch(...) requires a complete RFC 9485 I-Regexp source.'
11
+ : stateBudgetMessage('fullMatch(...)'));
9
12
  }
10
13
  return languageFullMatch(source);
11
14
  }
@@ -1,13 +1,9 @@
1
- import type { NodeClassReferenceInput } from '../class/authoring.js';
2
1
  import type { AUTHORING } from '../schema/handle.js';
3
- export type ViewDefinitionTargetsInput = readonly [
4
- NodeClassReferenceInput,
5
- ...NodeClassReferenceInput[]
6
- ];
7
- export type ViewTargetInput = 'domain' | NodeClassReferenceInput | ViewDefinitionTargetsInput;
2
+ /** A View always targets its owning Domain; it declares no Class or Node target. */
8
3
  export interface ViewInput {
9
4
  readonly description?: string;
10
- readonly target?: ViewTargetInput;
5
+ /** Removed: every View targets its owning Domain. Refused at compile time and at runtime. */
6
+ readonly target?: never;
11
7
  }
12
8
  export interface View {
13
9
  readonly [AUTHORING]: 'view';
@@ -1,4 +1,4 @@
1
- import { assertKnownKeys } from '../schema/diagnostic.js';
1
+ import { assertKnownKeys, fail } from '../schema/diagnostic.js';
2
2
  import { authoringValue } from '../schema/handle.js';
3
3
  const viewInputs = new WeakMap();
4
4
  /** Internal lowering access; View authoring handles expose no public configuration object. */
@@ -9,7 +9,10 @@ export function viewAuthoringInput(value) {
9
9
  return input;
10
10
  }
11
11
  export function view(config) {
12
- assertKnownKeys(config, ['description', 'target']);
12
+ if (Object.hasOwn(config, 'target')) {
13
+ fail('BUILDER_VIEW', '/target', 'View target is removed: every View targets its owning Domain. Select a Node inside the View frontend instead.');
14
+ }
15
+ assertKnownKeys(config, ['description']);
13
16
  const value = authoringValue('view', {});
14
17
  viewInputs.set(value, { ...config });
15
18
  return value;
@@ -8,6 +8,8 @@ import { admitStateTopology } from '../../language/state/topology.js';
8
8
  import { bindValueProgram } from '../../language/value/compile/load.js';
9
9
  import { isValueObject } from '../../language/value/model/object.js';
10
10
  import { classifyValueProgramOrdering } from '../../language/value/ordering.js';
11
+ import { compilePattern } from '../../language/value/regexp/compile.js';
12
+ import { isIRegexp } from '../../language/value/regexp/syntax.js';
11
13
  import { invalidCompiled } from '../diagnostic.js';
12
14
  import { revisionOfCompiledEntry } from './revision.js';
13
15
  import { validateCompiledSemantics } from './semantics.js';
@@ -24,7 +26,8 @@ export function acceptCompiledSchema(input) {
24
26
  if (root.version !== 2)
25
27
  invalid('version', undefined, '/version');
26
28
  const rootPin = dependency(root.root, 'root', '/root');
27
- const closure = array(root.closure, 'closure', '/closure').map((value, index) => entry(value, index, `/closure/${index}`));
29
+ const machines = new Map();
30
+ const closure = array(root.closure, 'closure', '/closure').map((value, index) => entry(value, index, `/closure/${index}`, machines));
28
31
  if (closure.length > dependencyLimits.closure) {
29
32
  invalid('dependency closure limit', 'COMPILED_DEPENDENCY_LIMITS', '/closure');
30
33
  }
@@ -74,7 +77,7 @@ export function acceptCompiledSchema(input) {
74
77
  validateCompiledSemantics(candidate);
75
78
  return deepFreeze(candidate);
76
79
  }
77
- function entry(input, entryIndex, path) {
80
+ function entry(input, entryIndex, path, machines) {
78
81
  const value = object(input, `closure[${entryIndex}]`, path);
79
82
  exact(value, ['origin', 'revision', 'dependencies', 'definitions', 'facts', 'values', 'patterns'], 'entry', path);
80
83
  const origin = originValue(value.origin, 'entry origin', `${path}/origin`);
@@ -89,7 +92,7 @@ function entry(input, entryIndex, path) {
89
92
  }
90
93
  const valuesPath = `${path}/values`;
91
94
  const patternsPath = `${path}/patterns`;
92
- const patterns = array(value.patterns, 'entry patterns', patternsPath).map((item, index) => pattern(item, index, `${patternsPath}/${index}`));
95
+ const patterns = array(value.patterns, 'entry patterns', patternsPath).map((item, index) => pattern(item, index, `${patternsPath}/${index}`, machines));
93
96
  const values = array(value.values, 'values', valuesPath).map((item, index) => program(item, index, `${valuesPath}/${index}`, patterns));
94
97
  const bound = values.map((value) => bindValueProgram(value, patterns));
95
98
  const definitionsPath = `${path}/definitions`;
@@ -195,6 +198,7 @@ function compiledProperty(input, values, node, path) {
195
198
  exact(value, ['name', 'required', 'value'], 'Compiled Property', path, [
196
199
  'auto',
197
200
  'description',
201
+ 'immutable',
198
202
  'ordering',
199
203
  'state',
200
204
  ]);
@@ -203,6 +207,9 @@ function compiledProperty(input, values, node, path) {
203
207
  description(value.description, 'Property description', `${path}/description`);
204
208
  if (value.auto !== undefined)
205
209
  propertyAuto(value.auto, `${path}/auto`);
210
+ if (value.immutable !== undefined && value.immutable !== 'create' && value.immutable !== 'once') {
211
+ invalid('Property immutability', undefined, `${path}/immutable`);
212
+ }
206
213
  const valueIndex = index(value.value, values.length, 'Property value', `${path}/value`);
207
214
  let state;
208
215
  if (value.state !== undefined) {
@@ -838,10 +845,28 @@ function valueNode(input, nodeIndex, path) {
838
845
  : { reference: integer(value.reference, 'reference', `${path}/reference`) }),
839
846
  };
840
847
  }
841
- function pattern(input, patternIndex, path) {
848
+ /**
849
+ * Admit one received pattern as data and return the machine compiled from its source. A received
850
+ * machine never decides membership: it may come from another compiler, from a host with other
851
+ * Unicode data, or from a forger, and the artifact carries no compiler version that could tell a
852
+ * current machine from those, so it is ignored rather than compared. Its source decides, through
853
+ * the same bounded `isIRegexp` and `compilePattern` that source resolution uses.
854
+ */
855
+ function pattern(input, patternIndex, path, machines) {
842
856
  const value = object(input, `patterns[${patternIndex}]`, path);
843
- if (Object.hasOwn(value, 'runs'))
844
- return runPattern(value, path);
857
+ const { source } = Object.hasOwn(value, 'runs')
858
+ ? runPattern(value, path)
859
+ : nfaPattern(value, path);
860
+ let machine = machines.get(source);
861
+ if (machine === undefined) {
862
+ if (!isIRegexp(source))
863
+ invalid('Pattern source', 'COMPILED_SOURCE_INVALID', `${path}/source`);
864
+ machine = compilePattern(source);
865
+ machines.set(source, machine);
866
+ }
867
+ return machine;
868
+ }
869
+ function nfaPattern(value, path) {
845
870
  exact(value, ['source', 'start', 'states'], 'Pattern', path);
846
871
  if (typeof value.source !== 'string')
847
872
  invalid('Pattern source', undefined, `${path}/source`);
@@ -21,10 +21,11 @@ export function validateCompiledSemantics(candidate) {
21
21
  const partial = index(candidate);
22
22
  const analyses = analyzeClasses(partial.classes);
23
23
  const context = { ...partial, analyses };
24
+ const admitted = admittedOrigins(candidate);
24
25
  validateClassFacets(context);
25
- validateEdges(context);
26
+ validateEdges(context, admitted);
26
27
  validateViews(candidate, context);
27
- validatePolicies(candidate, context);
28
+ validatePolicies(candidate, context, admitted);
28
29
  validateCore(candidate, context);
29
30
  }
30
31
  function index(candidate) {
@@ -161,6 +162,14 @@ function validateClassFacets(context) {
161
162
  fail(`${path}/auto/set`, 'The instant setter requires an Instant value program.');
162
163
  }
163
164
  }
165
+ if (property.immutable !== undefined) {
166
+ if (property.auto !== undefined || property.state !== undefined) {
167
+ fail(`${path}/immutable`, 'An immutable Property is neither automatic nor a State Property.');
168
+ }
169
+ if (property.immutable === 'once' && property.required) {
170
+ fail(`${path}/immutable`, 'A required immutable Property is declared create, not once.');
171
+ }
172
+ }
164
173
  }
165
174
  if (located.value.kind === 'node') {
166
175
  for (const [index, method] of located.value.methods.entries()) {
@@ -171,7 +180,30 @@ function validateClassFacets(context) {
171
180
  }
172
181
  }
173
182
  }
174
- function validateEdges(context) {
183
+ /**
184
+ * The origins each closure entry was admitted against: itself and its transitive dependencies.
185
+ * Source admission decides undirected overlap and types Policies within that closure alone, so a
186
+ * dependent's Class extending a dependency's Classes never reaches the dependency's own decisions.
187
+ */
188
+ function admittedOrigins(candidate) {
189
+ const entries = new Map(candidate.closure.map((entry) => [entry.origin, entry]));
190
+ const scopes = new Map();
191
+ const scope = (origin) => {
192
+ const known = scopes.get(origin);
193
+ if (known !== undefined)
194
+ return known;
195
+ const origins = new Set([origin]);
196
+ for (const dependency of Object.values(entries.get(origin)?.dependencies ?? {}))
197
+ for (const reached of scope(dependency.origin))
198
+ origins.add(reached);
199
+ scopes.set(origin, origins);
200
+ return origins;
201
+ };
202
+ for (const entry of candidate.closure)
203
+ scope(entry.origin);
204
+ return scopes;
205
+ }
206
+ function validateEdges(context, admitted) {
175
207
  for (const located of context.classes.values()) {
176
208
  if (!isCompiledEdge(located.value))
177
209
  continue;
@@ -194,7 +226,7 @@ function validateEdges(context) {
194
226
  }
195
227
  if (edge.orientation === 'undirected' &&
196
228
  !sameIncidentEndpoint(edge.endpoints[0], edge.endpoints[1]) &&
197
- undirectedOverlap(context, edge.endpoints)) {
229
+ undirectedOverlap(context, edge.endpoints, admitted.get(located.entry.origin))) {
198
230
  fail(`${located.pointer}/endpoints`, 'Unequal undirected endpoint contracts overlap.');
199
231
  }
200
232
  for (const [parentIndex, parentRef] of edge.extends.entries()) {
@@ -242,8 +274,9 @@ function sameIncidentEndpoint(left, right) {
242
274
  a.length === b.length &&
243
275
  a.every((value, index) => value === b[index]));
244
276
  }
245
- function undirectedOverlap(context, endpoints) {
246
- return [...context.classes.values()].some(({ value }) => value.kind === 'node' &&
277
+ function undirectedOverlap(context, endpoints, origins) {
278
+ return [...context.classes.values()].some(({ value }) => origins.has(value.ref.origin) &&
279
+ value.kind === 'node' &&
247
280
  !value.abstract &&
248
281
  classSatisfiesAny(context, value.ref, endpoints[0].accepts) &&
249
282
  classSatisfiesAny(context, value.ref, endpoints[1].accepts));
@@ -274,14 +307,14 @@ function validateViews(candidate, context) {
274
307
  }
275
308
  }
276
309
  }
277
- function validatePolicies(candidate, context) {
310
+ function validatePolicies(candidate, context, admitted) {
278
311
  for (const [entryIndex, entry] of candidate.closure.entries()) {
279
312
  const localPolicies = new Map(entry.definitions.policies.map((value) => [value.ref.name, value]));
280
313
  for (const [policyIndex, policy] of entry.definitions.policies.entries()) {
281
314
  validatePolicyExpression(context, entry, policy.expression, `/closure/${entryIndex}/definitions/policies/${policyIndex}/expression`, localPolicies);
282
315
  }
283
316
  validatePolicyCycles(entry, localPolicies, entryIndex);
284
- validatePolicyParity(context, entry, entryIndex, localPolicies);
317
+ validatePolicyParity(context, entry, entryIndex, localPolicies, admitted.get(entry.origin));
285
318
  for (const [functionIndex, callable] of entry.definitions.functions.entries()) {
286
319
  if (callable.policy !== undefined)
287
320
  validateCallablePolicy(context, entry, callable.policy, typeof callable.input === 'number' ? callable.input : callable.input.metadata, callable.fields, false, `/closure/${entryIndex}/definitions/functions/${functionIndex}/policy`);
@@ -455,9 +488,9 @@ function isNodeClassRef(context, value) {
455
488
  }
456
489
  return context.classes.get(Key.of(ref))?.value.kind === 'node';
457
490
  }
458
- function validatePolicyParity(context, entry, entryIndex, policies) {
491
+ function validatePolicyParity(context, entry, entryIndex, policies, origins) {
459
492
  const normalized = new Map([...policies].map(([name, policy]) => [name, normalizePolicyExpression(policy.expression)]));
460
- const typing = new CompiledPolicyTyping(context, entry, normalized);
493
+ const typing = new CompiledPolicyTyping(context, entry, normalized, origins);
461
494
  for (const [name, declaration] of policies) {
462
495
  const path = `/closure/${entryIndex}/definitions/policies/${declarationIndex(entry, 'policies', name)}`;
463
496
  if ('match' in declaration.expression) {
@@ -602,21 +635,21 @@ function nodePathAccepts(entry, input, field) {
602
635
  ? annotation.accepts
603
636
  : [];
604
637
  }
638
+ /** Types one entry's Policies against the Node Classes of its own closure, as source admission. */
605
639
  class CompiledPolicyTyping {
606
640
  context;
607
641
  entry;
608
642
  normalized;
609
643
  allNodeClasses;
610
644
  concreteNodeClasses;
611
- constructor(context, entry, normalized) {
645
+ constructor(context, entry, normalized, origins) {
612
646
  this.context = context;
613
647
  this.entry = entry;
614
648
  this.normalized = normalized;
615
- this.allNodeClasses = [...context.classes.values()]
616
- .filter(({ value }) => value.kind === 'node')
617
- .map(({ value }) => value.ref);
618
- this.concreteNodeClasses = [...context.classes.values()]
619
- .filter(({ value }) => value.kind === 'node' && !value.abstract)
649
+ const nodeClasses = [...context.classes.values()].filter(({ value }) => value.kind === 'node' && origins.has(value.ref.origin));
650
+ this.allNodeClasses = nodeClasses.map(({ value }) => value.ref);
651
+ this.concreteNodeClasses = nodeClasses
652
+ .filter(({ value }) => !value.abstract)
620
653
  .map(({ value }) => value.ref);
621
654
  }
622
655
  expandedBranches(name, visiting, namespace) {
@@ -54,6 +54,7 @@ function compileEntry(schema, lookup) {
54
54
  name,
55
55
  required: property.required,
56
56
  ...(property.auto === undefined ? {} : { auto: property.auto }),
57
+ ...(property.immutable === undefined ? {} : { immutable: property.immutable }),
57
58
  ...(property.description === undefined ? {} : { description: property.description }),
58
59
  ...(property.state === undefined ? {} : { state: property.state }),
59
60
  ...(ordering === undefined ? {} : { ordering }),
@@ -125,6 +125,7 @@ function programValidation(programs, properties, propertyOrdering, callables) {
125
125
  propertyValidation.set(key, Object.freeze({
126
126
  ordering: propertyOrdering.get(key),
127
127
  validate: (value) => validateValueProgram(program, value),
128
+ program: () => program,
128
129
  }));
129
130
  }
130
131
  const callableValidation = new Map();
@@ -104,24 +104,6 @@ export function createDomain(input) {
104
104
  definitions(kind) {
105
105
  return kind === undefined ? local : local.filter(({ ref }) => ref.kind === kind);
106
106
  },
107
- viewsFor(target) {
108
- return Object.freeze(Object.values(views).filter((view) => {
109
- if (view.target.kind === 'domain')
110
- return target === 'domain';
111
- if (target === 'domain')
112
- return false;
113
- return view.target.definitions.some((ref) => {
114
- const selected = classesByKey.get(Key.of(ref));
115
- if (target.kind === 'node' && selected?.kind === 'node') {
116
- return target.satisfies(selected);
117
- }
118
- if (target.kind === 'edge' && selected?.kind === 'edge') {
119
- return target.satisfies(selected);
120
- }
121
- return false;
122
- });
123
- }));
124
- },
125
107
  };
126
108
  const admitted = Object.freeze(domain);
127
109
  facades.set(entry.origin, admitted);
@@ -175,7 +157,6 @@ function createDefinitions(declarations, classesByKey, input) {
175
157
  definitionsByKey.set(key, Object.freeze({
176
158
  ref: definition.ref,
177
159
  key,
178
- target: definition.target,
179
160
  description: definition.description,
180
161
  definition,
181
162
  }));
@@ -36,7 +36,6 @@ export interface Domain<Classes extends Readonly<Record<string, unknown>> = Read
36
36
  callable(key: Key.Callable): ResolvedCallable | undefined;
37
37
  definitions(): readonly ResolvedDefinition[];
38
38
  definitions<const Kind extends DefinitionRef['kind']>(kind: Kind): readonly ResolvedDefinitionOf<Kind>[];
39
- viewsFor(target: 'domain' | AnyResolvedClass): readonly Views[keyof Views][];
40
39
  }
41
40
  declare const DOMAIN_SOURCE_RESOLVED: unique symbol;
42
41
  /** Type-only witness carried by a source that retains its exact resolved Domain. */
@@ -3,6 +3,7 @@ import type { DomainDependency, DomainDependencyMap } from '../../addressing/ind
3
3
  import type { CoreEdge } from '../../language/core/model/core.js';
4
4
  import type { Definition } from '../../language/definition.js';
5
5
  import type { Diagnostic } from '../../language/validation/diagnostic.js';
6
+ import type { ValueProgram } from '../../language/value/compile/model.js';
6
7
  import type { OrderingKind } from '../../language/value/ordering.js';
7
8
  /** One admitted Schema entry in a dependency-closed Domain universe. */
8
9
  export interface DomainEntryInput {
@@ -22,6 +23,8 @@ export interface DomainInput {
22
23
  export interface DomainValidation {
23
24
  propertyExists(key: PropertyKey): boolean;
24
25
  property(key: PropertyKey, input: unknown): readonly Diagnostic[];
26
+ /** The admitted program `property` evaluates. */
27
+ propertyProgram(key: PropertyKey): ValueProgram;
25
28
  propertyOrdering(key: PropertyKey): OrderingKind | undefined;
26
29
  callableExists(key: AddressKey.Callable): boolean;
27
30
  callableInput(key: AddressKey.Callable, input: unknown): readonly Diagnostic[];
@@ -45,6 +45,7 @@ export function createClasses(definitions, validation) {
45
45
  definition: propertyDefinition,
46
46
  ...(ordering === undefined ? {} : { ordering }),
47
47
  validate: (input) => validation.property(propertyKey, input),
48
+ program: () => validation.propertyProgram(propertyKey),
48
49
  });
49
50
  ownProperties.push(property);
50
51
  effectiveProperties.set(property.key, property);
@@ -1,10 +1,12 @@
1
1
  import type { Key, PropertyKey } from '../../addressing/index.js';
2
2
  import type { Diagnostic } from '../../language/validation/diagnostic.js';
3
+ import type { ValueProgram } from '../../language/value/compile/model.js';
3
4
  import type { OrderingKind } from '../../language/value/ordering.js';
4
5
  import type { DomainValidation } from '../model/input.js';
5
6
  export interface PropertyValidation {
6
7
  readonly ordering?: OrderingKind;
7
8
  validate(input: unknown): readonly Diagnostic[];
9
+ program(): ValueProgram;
8
10
  }
9
11
  export interface CallableValidation {
10
12
  readonly fields?: readonly string[];
@@ -6,6 +6,12 @@ export function createDomainValidation(properties, callables) {
6
6
  const target = properties.get(key);
7
7
  return target === undefined ? unknownTarget('Property', key) : target.validate(input);
8
8
  },
9
+ propertyProgram(key) {
10
+ const target = properties.get(key);
11
+ if (target === undefined)
12
+ throw new TypeError(`Property ${key} is absent.`);
13
+ return target.program();
14
+ },
9
15
  propertyOrdering: (key) => properties.get(key)?.ordering,
10
16
  callableExists: (key) => callables.has(key),
11
17
  callableInput(key, input) {
@@ -33,6 +39,12 @@ export function combineDomainValidation(values) {
33
39
  const owner = values.find((value) => value.propertyExists(key));
34
40
  return owner === undefined ? unknownTarget('Property', key) : owner.property(key, input);
35
41
  },
42
+ propertyProgram(key) {
43
+ const owner = values.find((value) => value.propertyExists(key));
44
+ if (owner === undefined)
45
+ throw new TypeError(`Property ${key} is absent.`);
46
+ return owner.propertyProgram(key);
47
+ },
36
48
  propertyOrdering: (key) => values.find((value) => value.propertyExists(key))?.propertyOrdering(key),
37
49
  callableExists: (key) => values.some((value) => value.callableExists(key)),
38
50
  callableInput(key, input) {
@@ -1,5 +1,7 @@
1
1
  export type AutoEvent = 'create' | 'update';
2
2
  export type AutoSet = 'instant';
3
+ /** `create`: written only by node or edge creation. `once`: written while absent, never unset. */
4
+ export type PropertyImmutability = 'create' | 'once';
3
5
  export interface PropertyAuto {
4
6
  readonly on: readonly AutoEvent[];
5
7
  readonly set: AutoSet;
@@ -8,6 +10,7 @@ export interface PropertyDefinition {
8
10
  readonly schema: import('../../value/model/schema.js').JsonSchema;
9
11
  readonly required: boolean;
10
12
  readonly auto?: PropertyAuto;
13
+ readonly immutable?: PropertyImmutability;
11
14
  readonly description?: string;
12
15
  readonly state?: import('../../state/index.js').StateTopology;
13
16
  }
@@ -3,6 +3,7 @@ import type { ClassRef, DefinitionKey, Key, PropertyKey } from '../../addressing
3
3
  import type { ResolvedMethodCollection } from '../callable/resolved.js';
4
4
  import type { StateTopology } from '../state/index.js';
5
5
  import type { Diagnostic } from '../validation/diagnostic.js';
6
+ import type { ValueProgram } from '../value/compile/model.js';
6
7
  import type { OrderingKind } from '../value/ordering.js';
7
8
  import type { ClassDefinition, ClassKind } from './model/class.js';
8
9
  import type { DataDefinition } from './model/data.js';
@@ -19,6 +20,8 @@ export interface ResolvedProperty<out Value = PortableValue, out Required extend
19
20
  /** The one portable scalar ordering domain, when the Property is orderable. */
20
21
  readonly ordering?: OrderingKind;
21
22
  validate(input: unknown): readonly Diagnostic[];
23
+ /** The admitted program `validate` evaluates. */
24
+ program(): ValueProgram;
22
25
  readonly __value?: Value;
23
26
  readonly __input?: Input;
24
27
  }
@@ -4,6 +4,8 @@ export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, F
4
4
  export type { Value as PropertyValue } from '@astrale-os/kernel-dsl/value';
5
5
  export type { JsonSchema as ValueSchema, JsonSchemaObject as ValueSchemaObject, } from './value/model/schema.js';
6
6
  export type { OrderingKind } from './value/ordering.js';
7
+ export type { ValueProgram } from './value/compile/model.js';
8
+ export { valueProgramIncludes } from './value/inclusion/includes.js';
7
9
  export { stateMachine } from './state/index.js';
8
10
  export type { EventOf, StateMachine, StateOf, StateTopology } from './state/index.js';
9
11
  export type * from './callable/model/callable.js';