@astrale-os/kernel-dsl 0.2.0-beta.37 → 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 (47) 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/core/model/core.d.ts +7 -1
  15. package/dist/v1/language/core/resolved.d.ts +6 -2
  16. package/dist/v1/language/index.d.ts +1 -1
  17. package/dist/v1/language/index.js +1 -1
  18. package/dist/v1/language/policy/index.d.ts +1 -1
  19. package/dist/v1/language/policy/index.js +1 -1
  20. package/dist/v1/language/policy/model/policy.d.ts +1 -0
  21. package/dist/v1/language/policy/model/policy.js +17 -0
  22. package/dist/v1/language/value/compile/fields.d.ts +1 -1
  23. package/dist/v1/language/value/compile/load.d.ts +4 -2
  24. package/dist/v1/language/value/compile/load.js +22 -1
  25. package/dist/v1/language/value/compile/model.d.ts +16 -1
  26. package/dist/v1/language/value/evaluate/assertions.d.ts +2 -0
  27. package/dist/v1/language/value/evaluate/assertions.js +13 -0
  28. package/dist/v1/language/value/evaluate/evaluator.js +12 -7
  29. package/dist/v1/language/value/evaluate/preparation.d.ts +10 -1
  30. package/dist/v1/language/value/evaluate/preparation.js +17 -26
  31. package/dist/v1/language/value/evaluate/program.js +1 -1
  32. package/dist/v1/language/value/evaluate/validate.d.ts +6 -2
  33. package/dist/v1/language/value/evaluate/validate.js +18 -11
  34. package/dist/v1/language/value/ordering.d.ts +2 -0
  35. package/dist/v1/language/value/ordering.js +4 -8
  36. package/dist/v1/language/value/regexp/compile.d.ts +3 -3
  37. package/dist/v1/language/value/regexp/compile.js +56 -11
  38. package/dist/v1/language/value/regexp/matcher.d.ts +3 -3
  39. package/dist/v1/language/value/regexp/matcher.js +75 -3
  40. package/dist/v1/schema/compatibility/footprint.js +6 -5
  41. package/dist/v1/schema/normalization/domain.js +15 -1
  42. package/dist/v1/schema/resolution/domain.js +17 -10
  43. package/dist/v1/schema/resolution/origins.d.ts +3 -0
  44. package/dist/v1/schema/resolution/origins.js +12 -4
  45. package/dist/v1/schema/validation/core.js +78 -26
  46. package/dist/v1/schema/validation/domain.structure.js +7 -2
  47. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { Key, MethodKey, PropertyKey as PropertyKeyValue } from '../../addressin
2
2
  import { compareUnicode } from '../../addressing/ordering.js';
3
3
  import { createDomain } from '../../domain/create.js';
4
4
  import { combineDomainValidation, createDomainValidation, unknownInputField, } from '../../domain/validation/create.js';
5
+ import { bindValueProgram } from '../../language/value/compile/load.js';
5
6
  import { validateValueProgram } from '../../language/value/evaluate/program.js';
6
7
  import { acceptCompiledSchema } from '../admission/accept.js';
7
8
  export function load(input) {
@@ -68,7 +69,9 @@ function loadEntry(entry) {
68
69
  ...entry.definitions.views,
69
70
  ...entry.definitions.core,
70
71
  ]),
71
- validation: programValidation(entry.values, propertyPrograms, propertyOrdering, callablePrograms),
72
+ validation: programValidation('patterns' in entry
73
+ ? entry.values.map((value) => bindValueProgram(value, entry.patterns))
74
+ : entry.values, propertyPrograms, propertyOrdering, callablePrograms),
72
75
  };
73
76
  }
74
77
  function loadMethod(entry, value) {
@@ -6,10 +6,10 @@ import type { ClassDefinition } from '../../language/class/model/class.js';
6
6
  import type { PropertyDefinition } from '../../language/class/model/property.js';
7
7
  import type { CoreDefinition, CoreEdge } from '../../language/core/model/core.js';
8
8
  import type { PolicyDefinition } from '../../language/policy/model/policy.js';
9
- import type { ValueProgram } from '../../language/value/compile/model.js';
9
+ import type { IndexedValueProgram, Pattern, RunPattern } from '../../language/value/compile/model.js';
10
10
  import type { OrderingKind } from '../../language/value/ordering.js';
11
11
  import type { ViewDefinition } from '../../language/view/model/view.js';
12
- export type { Pattern, PatternState, PatternTransition, ScalarRange, ValueNode, ValueProgram, } from '../../language/value/compile/model.js';
12
+ export type { Pattern, PatternState, PatternTransition, RunPattern, ScalarRun, ScalarRange, IndexedValueProgram, ValueNode, ValueProgram, } from '../../language/value/compile/model.js';
13
13
  export interface CompiledProperty extends Omit<PropertyDefinition, 'schema'> {
14
14
  readonly name: string;
15
15
  readonly value: number;
@@ -60,12 +60,13 @@ export interface CompiledSchemaEntry {
60
60
  readonly facts: {
61
61
  readonly core: readonly CoreEdge[];
62
62
  };
63
- readonly values: readonly ValueProgram[];
63
+ readonly values: readonly IndexedValueProgram[];
64
+ readonly patterns: readonly (Pattern | RunPattern)[];
64
65
  }
65
66
  declare const COMPILED_DOMAIN: unique symbol;
66
67
  export interface CompiledSchema<Resolved = Domain> {
67
68
  readonly format: 'astrale.dsl.compiled-schema';
68
- readonly version: 1;
69
+ readonly version: 2;
69
70
  readonly root: DomainDependency;
70
71
  readonly closure: readonly CompiledSchemaEntry[];
71
72
  readonly [COMPILED_DOMAIN]: Resolved;
@@ -1,4 +1,4 @@
1
- import { Key } from '../addressing/index.js';
1
+ import { Key, MethodKey } from '../addressing/index.js';
2
2
  import { frozenRecord } from './namespace/member-collection.js';
3
3
  import { createClasses } from './projection/classes.js';
4
4
  const admittedDomains = new WeakSet();
@@ -58,14 +58,13 @@ export function createDomain(input) {
58
58
  if (classValue?.kind !== 'edge') {
59
59
  throw new TypeError(`Core Edge has no Edge Class ${Key.of(edge.class)}.`);
60
60
  }
61
+ const endpoint = (ref) => ref.kind === 'domain'
62
+ ? domainEndpoint
63
+ : requiredCoreEndpoint(definitionsByKey, callables, ref);
61
64
  return Object.freeze({
62
65
  class: classValue,
63
- source: edge.source.kind === 'domain'
64
- ? domainEndpoint
65
- : requiredCore(definitionsByKey, edge.source),
66
- target: edge.target.kind === 'domain'
67
- ? domainEndpoint
68
- : requiredCore(definitionsByKey, edge.target),
66
+ source: endpoint(edge.source),
67
+ target: endpoint(edge.target),
69
68
  properties: edge.properties,
70
69
  });
71
70
  }));
@@ -241,9 +240,18 @@ function namespace(definitions, kind) {
241
240
  .filter((value) => value.ref.kind === kind)
242
241
  .map((value) => [value.ref.name, value]));
243
242
  }
244
- function requiredCore(definitions, ref) {
243
+ /** Every non-Domain Core endpoint addresses one already-resolved local Definition or Method. */
244
+ function requiredCoreEndpoint(definitions, callables, ref) {
245
+ if (ref.kind === 'method') {
246
+ const key = MethodKey.of(ref.owner, ref.name);
247
+ const method = callables.get(key);
248
+ if (method === undefined || !('owner' in method)) {
249
+ throw new TypeError(`Core endpoint ${key} is absent.`);
250
+ }
251
+ return method;
252
+ }
245
253
  const value = definitions.get(Key.of(ref));
246
- if (value?.ref.kind !== 'core')
254
+ if (value === undefined)
247
255
  throw new TypeError(`Core endpoint ${Key.of(ref)} is absent.`);
248
256
  return value;
249
257
  }
@@ -4,7 +4,7 @@ export { stateMachine } from './language/state/index.js';
4
4
  export type { EventOf, StateMachine, StateOf } from './language/state/index.js';
5
5
  export { PropertyValues } from './language/index.js';
6
6
  export * as metadata from './language/definition/metadata.js';
7
- export type { AnyResolvedClass, BoundMethodOf, BoundMethodShortcutsOf, CallableResultOf, CallableInputOf, CallableOutputOf, ClassSatisfactionKeysOf, ClassDefinition, CoreDefinition, CoreEdge, FunctionAuth, FunctionDefinition, MethodDefinition, MethodOwnerKeyOf, PolicyCheckExpression, PolicyDefinition, PropertyInputOf, PropertyAccessOf, PropertyOutputOf, PropertyShortcutsOf, PropertyValuesOf, ResolvedClass, ResolvedCoreDefinition, ResolvedData, ResolvedFunction, ExecutableMethod, ResolvedMethod, ResolvedMethodCollection, ResolvedPolicy, ResolvedProperty, ResolvedView, MethodAccessOf, SelfMethodsOf, SelfPropertiesOf, SelfStaticMethodsOf, StaticMethodsOf, ViewDefinition, } from './language/index.js';
7
+ export type { AnyResolvedClass, BoundMethodOf, BoundMethodShortcutsOf, CallableResultOf, CallableInputOf, CallableOutputOf, ClassSatisfactionKeysOf, ClassDefinition, CoreDefinition, CoreEdge, FunctionAuth, FunctionDefinition, MethodDefinition, MethodOwnerKeyOf, PolicyCheckExpression, PolicyDefinition, PropertyInputOf, PropertyAccessOf, PropertyOutputOf, PropertyShortcutsOf, PropertyValuesOf, ResolvedClass, ResolvedCoreDefinition, ResolvedCoreEndpoint, ResolvedData, ResolvedFunction, ExecutableMethod, ResolvedMethod, ResolvedMethodCollection, ResolvedPolicy, ResolvedProperty, ResolvedView, MethodAccessOf, SelfMethodsOf, SelfPropertiesOf, SelfStaticMethodsOf, StaticMethodsOf, ViewDefinition, } from './language/index.js';
8
8
  export { acceptDefinitionRef, callableAddress, ClassKey, equalDefinitionRefs, isDefinitionRef, Key, MethodKey, PropertyKey, } from './addressing/index.js';
9
9
  export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, FunctionRef, MethodAddress, PolicyRef, Revision, ViewRef, } from './addressing/index.js';
10
10
  export type { Definition } from './language/index.js';
@@ -1,9 +1,15 @@
1
1
  import type { ClassRef, CoreRef, DefinitionBase } from '../../../addressing/index.js';
2
2
  import type { PropertyValues } from '../../class/property-values.js';
3
+ import type { PolicyObjectRef } from '../../policy/model/policy.js';
3
4
  export interface DomainEndpointRef {
4
5
  readonly kind: 'domain';
5
6
  }
6
- export type CoreEndpointRef = CoreRef | DomainEndpointRef;
7
+ /**
8
+ * One Core Edge endpoint: the owning Domain itself, or any local Definition whose installed
9
+ * projection resolves through an exact member Path. That is the same projected-member vocabulary
10
+ * a Policy object names, so both languages address one identity the same way.
11
+ */
12
+ export type CoreEndpointRef = PolicyObjectRef | DomainEndpointRef;
7
13
  export interface CoreDefinition extends DefinitionBase<CoreRef> {
8
14
  readonly class: ClassRef;
9
15
  readonly properties: PropertyValues;
@@ -1,6 +1,8 @@
1
1
  import type { DefinitionKey } from '../../addressing/index.js';
2
+ import type { ResolvedMethod } from '../callable/resolved.js';
2
3
  import type { PropertyValues } from '../class/property-values.js';
3
4
  import type { ResolvedClass } from '../class/resolved.js';
5
+ import type { ResolvedDefinition } from '../resolved.js';
4
6
  import type { CoreDefinition } from './model/core.js';
5
7
  export interface ResolvedCoreDefinition {
6
8
  readonly ref: CoreDefinition['ref'];
@@ -13,9 +15,11 @@ export interface ResolvedDomainEndpoint {
13
15
  readonly kind: 'domain';
14
16
  readonly origin: string;
15
17
  }
18
+ /** The owning Domain, one local Definition, or one local Class-owned Method. */
19
+ export type ResolvedCoreEndpoint = ResolvedDefinition | ResolvedMethod | ResolvedDomainEndpoint;
16
20
  export interface ResolvedCoreEdge {
17
21
  readonly class: ResolvedClass<'edge'>;
18
- readonly source: ResolvedCoreDefinition | ResolvedDomainEndpoint;
19
- readonly target: ResolvedCoreDefinition | ResolvedDomainEndpoint;
22
+ readonly source: ResolvedCoreEndpoint;
23
+ readonly target: ResolvedCoreEndpoint;
20
24
  readonly properties: PropertyValues;
21
25
  }
@@ -21,7 +21,7 @@ export type { Definition } from './definition.js';
21
21
  export type { ResolvedDefinition, ResolvedDefinitionOf } from './resolved.js';
22
22
  export type { Diagnostic, DiagnosticCode } from './validation/diagnostic.js';
23
23
  export type * from './policy/model/policy.js';
24
- export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, } from './policy/model/policy.js';
24
+ export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './policy/model/policy.js';
25
25
  export type * from './policy/resolved.js';
26
26
  export type * from './view/model/view.js';
27
27
  export type * from './view/resolved.js';
@@ -1,4 +1,4 @@
1
1
  export { isMethodExecutable } from './callable/executable.js';
2
2
  export { stateMachine } from './state/index.js';
3
3
  export { PropertyValues } from './class/property-values.js';
4
- export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, } from './policy/model/policy.js';
4
+ export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './policy/model/policy.js';
@@ -1,4 +1,4 @@
1
1
  export type * from './model/policy.js';
2
- export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, } from './model/policy.js';
2
+ export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './model/policy.js';
3
3
  export type * from './resolved.js';
4
4
  export * from './normalization/normalize.js';
@@ -1,2 +1,2 @@
1
- export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, } from './model/policy.js';
1
+ export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './model/policy.js';
2
2
  export * from './normalization/normalize.js';
@@ -89,6 +89,7 @@ export type PolicyObjectRef = ClassRef | FunctionRef | PolicyRef | ViewRef | Cor
89
89
  /** Exact projected identity, including Class-owned Method declarations. */
90
90
  export declare function policyObjectKey(ref: PolicyObjectRef): string;
91
91
  export declare function policyObjectOrigin(ref: PolicyObjectRef): string;
92
+ export declare function projectedMemberClass(ref: Exclude<PolicyObjectRef, CoreRef>): ClassRef;
92
93
  export type PolicyCheckObject = {
93
94
  readonly kind: 'self';
94
95
  } | {
@@ -16,3 +16,20 @@ export function policyObjectKey(ref) {
16
16
  export function policyObjectOrigin(ref) {
17
17
  return ref.kind === 'method' ? ref.owner.origin : ref.origin;
18
18
  }
19
+ /**
20
+ * The Kernel declaration Class each projected member materializes as. A Core Node is absent: it
21
+ * keeps the concrete Class its own Domain authored, which only that Domain's schema can resolve.
22
+ */
23
+ const PROJECTED_MEMBER_CLASSES = Object.freeze({
24
+ class: kernelClassRef('Class'),
25
+ function: kernelClassRef('Function'),
26
+ method: kernelClassRef('Function'),
27
+ policy: kernelClassRef('Policy'),
28
+ view: kernelClassRef('View'),
29
+ });
30
+ export function projectedMemberClass(ref) {
31
+ return PROJECTED_MEMBER_CLASSES[ref.kind];
32
+ }
33
+ function kernelClassRef(name) {
34
+ return Object.freeze({ origin: 'kernel.astrale.ai', kind: 'class', name });
35
+ }
@@ -1,3 +1,3 @@
1
1
  import type { ValueProgram } from './model.js';
2
2
  /** Exact field constraints available for independent scalar route validation. */
3
- export declare function inputFieldNodes(program: ValueProgram): Readonly<Record<string, readonly number[]>> | undefined;
3
+ export declare function inputFieldNodes(program: Pick<ValueProgram, 'root' | 'nodes'>): Readonly<Record<string, readonly number[]>> | undefined;
@@ -1,4 +1,6 @@
1
1
  import type { CompiledInstanceSchema } from '../evaluate/types.js';
2
- import type { ValueProgram } from './model.js';
2
+ import type { IndexedValueProgram, ValueProgram } from './model.js';
3
+ /** Bind already-admitted entry-local indices without copying any pattern machine. */
4
+ export declare function bindValueProgram(program: IndexedValueProgram, patterns: ValueProgram['patterns']): ValueProgram;
3
5
  /** Bind admitted portable indices to the canonical instance evaluator. */
4
- export declare function loadValueProgram(program: ValueProgram): CompiledInstanceSchema;
6
+ export declare function loadValueProgram(program: ValueProgram, rootIndex?: number): CompiledInstanceSchema;
@@ -1,7 +1,28 @@
1
1
  import { loadPattern } from '../regexp/matcher.js';
2
2
  import { appendPointer } from '../syntax/pointer.js';
3
+ const loadedPrograms = new WeakMap();
4
+ /** Bind already-admitted entry-local indices without copying any pattern machine. */
5
+ export function bindValueProgram(program, patterns) {
6
+ return Object.freeze({
7
+ ...program,
8
+ patterns: Object.freeze(program.patterns.map((index) => patterns[index])),
9
+ });
10
+ }
3
11
  /** Bind admitted portable indices to the canonical instance evaluator. */
4
- export function loadValueProgram(program) {
12
+ export function loadValueProgram(program, rootIndex = program.root) {
13
+ let compiled = loadedPrograms.get(program);
14
+ if (compiled === undefined) {
15
+ compiled = createValueProgram(program);
16
+ loadedPrograms.set(program, compiled);
17
+ }
18
+ if (rootIndex === program.root)
19
+ return compiled;
20
+ const root = compiled.locations.get(String(rootIndex));
21
+ if (root === undefined)
22
+ throw new TypeError('Compiled value program root is absent.');
23
+ return { ...compiled, root };
24
+ }
25
+ function createValueProgram(program) {
5
26
  const resource = { key: 'compiled', schema: true, ownerOrigin: 'compiled' };
6
27
  const locations = program.nodes.map((node, index) => ({
7
28
  key: String(index),
@@ -14,6 +14,17 @@ export interface Pattern {
14
14
  readonly start: number;
15
15
  readonly states: readonly PatternState[];
16
16
  }
17
+ /** A scalar run counts Unicode scalars, never UTF-16 code units. */
18
+ export interface ScalarRun {
19
+ readonly ranges: readonly ScalarRange[];
20
+ readonly minimum: number;
21
+ readonly maximum?: number;
22
+ }
23
+ /** All runs except the last have an exact finite length. */
24
+ export interface RunPattern {
25
+ readonly source: string;
26
+ readonly runs: readonly ScalarRun[];
27
+ }
17
28
  export interface ValueNode {
18
29
  readonly schema: JsonValue;
19
30
  readonly children: Readonly<Record<string, number>>;
@@ -23,5 +34,9 @@ export interface ValueNode {
23
34
  export interface ValueProgram {
24
35
  readonly root: number;
25
36
  readonly nodes: readonly ValueNode[];
26
- readonly patterns: readonly Pattern[];
37
+ readonly patterns: readonly (Pattern | RunPattern)[];
38
+ }
39
+ /** Pattern indices belong to the enclosing compiled entry's table. */
40
+ export interface IndexedValueProgram extends Omit<ValueProgram, 'patterns'> {
41
+ readonly patterns: readonly number[];
27
42
  }
@@ -1,6 +1,8 @@
1
1
  import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
2
  export declare function matchesType(input: string, value: JsonValue): boolean;
3
3
  export declare function jsonEqual(left: JsonValue, right: JsonValue): boolean;
4
+ /** Count Unicode code points with string-iterator semantics, without a character array. */
5
+ export declare function codePointLength(input: string): number;
4
6
  export declare function unique(values: readonly JsonValue[]): boolean;
5
7
  /** Compare finite JSON numbers as their exact ECMAScript decimal spellings. */
6
8
  export declare function isMultipleOf(value: number, divisor: number): boolean;
@@ -21,6 +21,19 @@ export function matchesType(input, value) {
21
21
  export function jsonEqual(left, right) {
22
22
  return equal(left, right);
23
23
  }
24
+ /** Count Unicode code points with string-iterator semantics, without a character array. */
25
+ export function codePointLength(input) {
26
+ let length = 0;
27
+ for (let index = 0; index < input.length; index++, length++) {
28
+ const first = input.charCodeAt(index);
29
+ if (first >= 0xd800 && first <= 0xdbff && index + 1 < input.length) {
30
+ const second = input.charCodeAt(index + 1);
31
+ if (second >= 0xdc00 && second <= 0xdfff)
32
+ index++;
33
+ }
34
+ }
35
+ return length;
36
+ }
24
37
  export function unique(values) {
25
38
  const seen = new Set();
26
39
  for (const value of values) {
@@ -1,7 +1,7 @@
1
1
  import { isInstantValue } from '../model/instant.js';
2
2
  import { isValueObject as isJsonObject } from '../model/object.js';
3
3
  import { appendPointer } from '../syntax/pointer.js';
4
- import { isMultipleOf, jsonEqual, matchesType, unique } from './assertions.js';
4
+ import { codePointLength, isMultipleOf, jsonEqual, matchesType, unique } from './assertions.js';
5
5
  import { evaluation, mergeAnnotations } from './types.js';
6
6
  /** Evaluate complete Draft 2020-12 acceptance without dynamic source generation. */
7
7
  export function evaluateInstance(compiled, input) {
@@ -154,12 +154,17 @@ class InstanceEvaluator {
154
154
  }
155
155
  }
156
156
  evaluateStringAssertions(schema, input, instancePointer, failures) {
157
- const length = Array.from(input).length;
158
- if (typeof schema.maxLength === 'number' && length > schema.maxLength) {
159
- failures.push(invalid(instancePointer, 'maxLength', 'String exceeds maxLength.'));
160
- }
161
- if (typeof schema.minLength === 'number' && length < schema.minLength) {
162
- failures.push(invalid(instancePointer, 'minLength', 'String is below minLength.'));
157
+ // For n UTF-16 units, ceil(n / 2) <= code points <= n. Count only if those
158
+ // bounds cannot already prove both length assertions; other assertions still run.
159
+ if ((typeof schema.maxLength === 'number' && input.length > schema.maxLength) ||
160
+ (typeof schema.minLength === 'number' && Math.ceil(input.length / 2) < schema.minLength)) {
161
+ const length = codePointLength(input);
162
+ if (typeof schema.maxLength === 'number' && length > schema.maxLength) {
163
+ failures.push(invalid(instancePointer, 'maxLength', 'String exceeds maxLength.'));
164
+ }
165
+ if (typeof schema.minLength === 'number' && length < schema.minLength) {
166
+ failures.push(invalid(instancePointer, 'minLength', 'String is below minLength.'));
167
+ }
163
168
  }
164
169
  if (typeof schema.pattern === 'string' && !this.compiled.matcher(schema.pattern).test(input)) {
165
170
  failures.push(invalid(instancePointer, 'pattern', 'String does not match pattern.'));
@@ -1,6 +1,8 @@
1
1
  import type { JsonSchema } from '../model/schema.js';
2
2
  import type { ValueSchemaRegistry } from '../reference/registry.js';
3
+ import type { IRegexpMatcher } from '../regexp/matcher.js';
3
4
  import type { CompiledInstanceSchema } from './types.js';
5
+ import { compilePattern } from '../regexp/compile.js';
4
6
  export type InstanceSchemaCompilation = {
5
7
  readonly compiled: true;
6
8
  readonly value: CompiledInstanceSchema;
@@ -8,6 +10,13 @@ export type InstanceSchemaCompilation = {
8
10
  readonly compiled: false;
9
11
  readonly message: string;
10
12
  };
13
+ /** Owned by one compilation or source-resolution closure, not retained process-globally. */
14
+ interface PreparedPattern {
15
+ readonly pattern: ReturnType<typeof compilePattern>;
16
+ readonly matcher: IRegexpMatcher;
17
+ }
18
+ export type PatternPool = Map<string, PreparedPattern>;
11
19
  /** Prepare reference and linear-regexp state without generating executable source. */
12
- export declare function prepareValueSchema(schema: JsonSchema, registry: ValueSchemaRegistry, pointer: string): InstanceSchemaCompilation;
20
+ export declare function prepareValueSchema(schema: JsonSchema, registry: ValueSchemaRegistry, pointer: string, pool?: PatternPool): InstanceSchemaCompilation;
13
21
  export declare function childKey(pointer: string): string;
22
+ export {};
@@ -6,7 +6,7 @@ import { loadPattern } from '../regexp/matcher.js';
6
6
  import { appendPointer } from '../syntax/pointer.js';
7
7
  import { walkSchema } from '../syntax/walk.js';
8
8
  /** Prepare reference and linear-regexp state without generating executable source. */
9
- export function prepareValueSchema(schema, registry, pointer) {
9
+ export function prepareValueSchema(schema, registry, pointer, pool = new Map()) {
10
10
  const indexed = indexRegistry(registry, pointer);
11
11
  if (indexed.diagnostics.length > 0)
12
12
  return failed(indexed.diagnostics);
@@ -26,11 +26,11 @@ export function prepareValueSchema(schema, registry, pointer) {
26
26
  });
27
27
  if (!isJsonObject(location.schema))
28
28
  return;
29
- rememberMatcher(matchers, location.schema.pattern);
30
- rememberMatcher(matchers, location.schema.fullMatch);
29
+ rememberMatcher(matchers, pool, location.schema.pattern);
30
+ rememberMatcher(matchers, pool, location.schema.fullMatch);
31
31
  if (isJsonObject(location.schema.patternProperties)) {
32
32
  for (const pattern of Object.keys(location.schema.patternProperties)) {
33
- rememberMatcher(matchers, pattern);
33
+ rememberMatcher(matchers, pool, pattern);
34
34
  }
35
35
  }
36
36
  });
@@ -40,13 +40,14 @@ export function prepareValueSchema(schema, registry, pointer) {
40
40
  if (root === undefined) {
41
41
  return { compiled: false, message: 'The root value schema is absent from its closure.' };
42
42
  }
43
+ let program;
43
44
  return {
44
45
  compiled: true,
45
46
  value: {
46
47
  root,
47
48
  locations,
48
49
  resolutions: closure.resolutions,
49
- program: () => portableProgram(root, locations, closure.resolutions),
50
+ program: () => (program ??= portableProgram(root, locations, closure.resolutions, matchers)),
50
51
  child(location, keyword, member) {
51
52
  const keywordPointer = appendPointer(location.pointer, keyword);
52
53
  const pointer = member === undefined ? keywordPointer : appendPointer(keywordPointer, member);
@@ -67,15 +68,14 @@ export function prepareValueSchema(schema, registry, pointer) {
67
68
  if (matcher === undefined) {
68
69
  throw new TypeError('Value schema uses an unprepared I-Regexp source.');
69
70
  }
70
- return matcher;
71
+ return matcher.matcher;
71
72
  },
72
73
  },
73
74
  };
74
75
  }
75
- function portableProgram(root, locations, resolutions) {
76
+ function portableProgram(root, locations, resolutions, patterns) {
76
77
  const ordered = [...locations.values()];
77
78
  const indices = new Map(ordered.map((location, index) => [location.key, index]));
78
- const patterns = new Map();
79
79
  const nodes = ordered.map((location) => {
80
80
  const children = {};
81
81
  walkSchema(location.schema, (child) => {
@@ -85,7 +85,6 @@ function portableProgram(root, locations, resolutions) {
85
85
  if (target !== undefined)
86
86
  children[childKey(child.pointer)] = target;
87
87
  });
88
- rememberPattern(patterns, location.schema);
89
88
  const resolved = resolutions.get(location.key);
90
89
  const reference = resolved === undefined
91
90
  ? undefined
@@ -102,29 +101,21 @@ function portableProgram(root, locations, resolutions) {
102
101
  return Object.freeze({
103
102
  root: indices.get(root.key),
104
103
  nodes: Object.freeze(nodes),
105
- patterns: Object.freeze([...patterns.values()]),
104
+ patterns: Object.freeze([...patterns.values()].map(({ pattern }) => pattern)),
106
105
  });
107
106
  }
108
- function rememberPattern(patterns, schema) {
109
- if (!isJsonObject(schema))
110
- return;
111
- for (const source of [schema.pattern, schema.fullMatch]) {
112
- if (typeof source === 'string' && !patterns.has(source))
113
- patterns.set(source, compilePattern(source));
114
- }
115
- if (isJsonObject(schema.patternProperties)) {
116
- for (const source of Object.keys(schema.patternProperties)) {
117
- if (!patterns.has(source))
118
- patterns.set(source, compilePattern(source));
119
- }
120
- }
121
- }
122
107
  export function childKey(pointer) {
123
108
  return pointer.slice(1);
124
109
  }
125
- function rememberMatcher(matchers, input) {
110
+ function rememberMatcher(matchers, pool, input) {
126
111
  if (typeof input === 'string' && !matchers.has(input)) {
127
- matchers.set(input, loadPattern(compilePattern(input)));
112
+ let prepared = pool.get(input);
113
+ if (prepared === undefined) {
114
+ const pattern = compilePattern(input);
115
+ prepared = { pattern, matcher: loadPattern(pattern) };
116
+ pool.set(input, prepared);
117
+ }
118
+ matchers.set(input, prepared);
128
119
  }
129
120
  }
130
121
  function failed(diagnostics) {
@@ -6,7 +6,7 @@ export function validateValueProgram(program, input, roots = [program.root]) {
6
6
  if (canonical.length > 0)
7
7
  return canonical;
8
8
  return roots.flatMap((root) => {
9
- const result = evaluateInstance(loadValueProgram({ ...program, root }), input);
9
+ const result = evaluateInstance(loadValueProgram(program, root), input);
10
10
  return result.failures.map((failure) => ({
11
11
  code: 'VALUE_SCHEMA_INSTANCE_INVALID',
12
12
  pointer: failure.instancePointer,
@@ -2,11 +2,15 @@ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
2
  import type { Diagnostic } from '../../validation/diagnostic.js';
3
3
  import type { JsonSchema } from '../model/schema.js';
4
4
  import type { ValueSchemaRegistry } from '../reference/registry.js';
5
+ import type { CompiledInstanceSchema } from './types.js';
6
+ import { type PatternPool } from './preparation.js';
5
7
  /**
6
8
  * Validate one complete portable value with the admitted V1 Draft 2020-12 profile.
7
9
  *
8
10
  * Evaluation is an interpreter over the exact local resource closure. It never performs network
9
- * resolution or dynamic source generation, and every admitted I-Regexp executes through RE2JS.
11
+ * resolution or dynamic source generation; patterns use the portable executor.
10
12
  * `format` remains annotation-only; the graph NodePath codec performs its separate locator check.
11
13
  */
12
- export declare function validateJsonInstance(schema: JsonSchema, value: JsonValue, registry: ValueSchemaRegistry, pointer?: string): Diagnostic[];
14
+ export declare function validateJsonInstance(schema: JsonSchema, value: JsonValue, registry: ValueSchemaRegistry, pointer?: string, patterns?: PatternPool): Diagnostic[];
15
+ /** Validate with the exact preparation already retained by a source-resolved Property or Callable. */
16
+ export declare function validatePreparedInstance(compiled: CompiledInstanceSchema, value: unknown): Diagnostic[];
@@ -1,31 +1,41 @@
1
1
  import { diagnostic } from '../../validation/diagnostic.js';
2
2
  import { validateCanonicalJsonDomain } from '../profile/json.validate.js';
3
3
  import { evaluateInstance } from './evaluator.js';
4
- import { prepareValueSchema } from './preparation.js';
4
+ import { prepareValueSchema, } from './preparation.js';
5
5
  const validatorCache = new WeakMap();
6
6
  /**
7
7
  * Validate one complete portable value with the admitted V1 Draft 2020-12 profile.
8
8
  *
9
9
  * Evaluation is an interpreter over the exact local resource closure. It never performs network
10
- * resolution or dynamic source generation, and every admitted I-Regexp executes through RE2JS.
10
+ * resolution or dynamic source generation; patterns use the portable executor.
11
11
  * `format` remains annotation-only; the graph NodePath codec performs its separate locator check.
12
12
  */
13
- export function validateJsonInstance(schema, value, registry, pointer = '') {
13
+ export function validateJsonInstance(schema, value, registry, pointer = '', patterns) {
14
14
  const diagnostics = [...validateCanonicalJsonDomain(value, pointer)];
15
15
  if (diagnostics.length > 0)
16
16
  return diagnostics;
17
- const prepared = preparedInstanceValidator(schema, registry, pointer);
18
- if (prepared.kind === 'error') {
17
+ const prepared = preparedInstanceValidator(schema, registry, pointer, patterns);
18
+ if (!prepared.compiled) {
19
19
  return [
20
20
  diagnostic('VS_REFERENCE_RESOLUTION', pointer, `Value validation failed closed while preparing exact schema resources: ${prepared.message}.`),
21
21
  ];
22
22
  }
23
- const result = evaluateInstance(prepared.compiled, value);
23
+ return instanceDiagnostics(prepared.value, value, pointer);
24
+ }
25
+ /** Validate with the exact preparation already retained by a source-resolved Property or Callable. */
26
+ export function validatePreparedInstance(compiled, value) {
27
+ const diagnostics = [...validateCanonicalJsonDomain(value, '')];
28
+ return diagnostics.length > 0
29
+ ? diagnostics
30
+ : instanceDiagnostics(compiled, value, '');
31
+ }
32
+ function instanceDiagnostics(compiled, value, pointer) {
33
+ const result = evaluateInstance(compiled, value);
24
34
  if (result.valid)
25
35
  return [];
26
36
  return result.failures.map((failure) => diagnostic('VALUE_SCHEMA_INSTANCE_INVALID', `${pointer}${failure.instancePointer}`, `Value does not satisfy its complete V1 schema at ${failure.keyword}: ${failure.message}`));
27
37
  }
28
- function preparedInstanceValidator(schema, registry, pointer) {
38
+ function preparedInstanceValidator(schema, registry, pointer, patterns) {
29
39
  let bySchema = validatorCache.get(registry);
30
40
  if (bySchema === undefined) {
31
41
  bySchema = new Map();
@@ -34,10 +44,7 @@ function preparedInstanceValidator(schema, registry, pointer) {
34
44
  const cached = bySchema.get(schema);
35
45
  if (cached !== undefined)
36
46
  return cached;
37
- const compilation = prepareValueSchema(schema, registry, pointer);
38
- const prepared = compilation.compiled
39
- ? { kind: 'validator', compiled: compilation.value }
40
- : { kind: 'error', message: compilation.message };
47
+ const prepared = prepareValueSchema(schema, registry, pointer, patterns);
41
48
  bySchema.set(schema, prepared);
42
49
  return prepared;
43
50
  }
@@ -1,4 +1,5 @@
1
1
  import type { ValueProgram } from './compile/model.js';
2
+ import type { CompiledInstanceSchema } from './evaluate/types.js';
2
3
  import type { JsonSchema } from './model/schema.js';
3
4
  import type { ValueSchemaRegistry } from './reference/registry.js';
4
5
  export type OrderingKind = 'boolean' | 'number' | 'string' | 'instant';
@@ -11,3 +12,4 @@ export type OrderingKind = 'boolean' | 'number' | 'string' | 'instant';
11
12
  export declare function classifyOrdering(contract: JsonSchema, registry: ValueSchemaRegistry): OrderingKind | undefined;
12
13
  /** Recompute the scalar ordering classification from one admitted portable value program. */
13
14
  export declare function classifyValueProgramOrdering(program: ValueProgram): OrderingKind | undefined;
15
+ export declare function classifyPreparedOrdering(compiled: CompiledInstanceSchema): OrderingKind | undefined;
@@ -18,17 +18,13 @@ export function classifyOrdering(contract, registry) {
18
18
  const prepared = prepareValueSchema(contract, registry, '');
19
19
  if (!prepared.compiled)
20
20
  return undefined;
21
- const candidates = new OrderingClassifier(prepared.value).classify(prepared.value.root);
22
- if (candidates.size !== 1)
23
- return undefined;
24
- const [kind] = candidates;
25
- return kind === 'boolean' || kind === 'number' || kind === 'string' || kind === 'instant'
26
- ? kind
27
- : undefined;
21
+ return classifyPreparedOrdering(prepared.value);
28
22
  }
29
23
  /** Recompute the scalar ordering classification from one admitted portable value program. */
30
24
  export function classifyValueProgramOrdering(program) {
31
- const compiled = loadValueProgram(program);
25
+ return classifyPreparedOrdering(loadValueProgram(program));
26
+ }
27
+ export function classifyPreparedOrdering(compiled) {
32
28
  const candidates = new OrderingClassifier(compiled).classify(compiled.root);
33
29
  if (candidates.size !== 1)
34
30
  return undefined;
@@ -1,3 +1,3 @@
1
- import type { Pattern } from '../compile/model.js';
2
- /** Compile one admitted I-Regexp AST to portable Thompson-NFA data. */
3
- export declare function compilePattern(source: string): Pattern;
1
+ import type { Pattern, RunPattern } from '../compile/model.js';
2
+ /** Compile scalar sequences as counted runs; preserve the NFA for branching expressions. */
3
+ export declare function compilePattern(source: string): Pattern | RunPattern;