@astrale-os/kernel-dsl 1.0.0-beta.13 → 1.0.0-beta.14

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 (66) hide show
  1. package/dist/v1/addressing/key.js +2 -1
  2. package/dist/v1/addressing/patterns.d.ts +1 -0
  3. package/dist/v1/addressing/patterns.js +2 -1
  4. package/dist/v1/builder/class/property.d.ts +3 -9
  5. package/dist/v1/builder/class/property.js +1 -0
  6. package/dist/v1/builder/lowering/class.js +5 -1
  7. package/dist/v1/builder/lowering/policy.js +1 -0
  8. package/dist/v1/builder/policy/authoring.js +3 -2
  9. package/dist/v1/bundle/codec.js +2 -2
  10. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  11. package/dist/v1/bundle/diagnostic.js +6 -0
  12. package/dist/v1/bundle/structure.js +2 -2
  13. package/dist/v1/domain/validation/create.d.ts +4 -1
  14. package/dist/v1/domain/validation/create.js +21 -34
  15. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  16. package/dist/v1/language/class/model/class.d.ts +4 -4
  17. package/dist/v1/language/class/model/edge.d.ts +2 -2
  18. package/dist/v1/language/class/model/property.d.ts +16 -7
  19. package/dist/v1/language/definition/metadata.d.ts +5 -0
  20. package/dist/v1/language/definition/metadata.js +7 -0
  21. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  22. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  23. package/dist/v1/language/validation/diagnostic.d.ts +1 -1
  24. package/dist/v1/language/validation/rule.d.ts +26 -0
  25. package/dist/v1/language/validation/rule.js +116 -0
  26. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  27. package/dist/v1/language/value/profile/rules.js +13 -37
  28. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  29. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  30. package/dist/v1/language/value/reference/registry.index.js +2 -1
  31. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  32. package/dist/v1/language/value/syntax/keywords.js +4 -20
  33. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  34. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  35. package/dist/v1/schema/analysis/class.js +33 -15
  36. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  37. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  38. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  39. package/dist/v1/schema/diagnostic.d.ts +17 -3
  40. package/dist/v1/schema/diagnostic.js +11 -0
  41. package/dist/v1/schema/index.d.ts +3 -1
  42. package/dist/v1/schema/index.js +1 -0
  43. package/dist/v1/schema/model/schema.d.ts +19 -1
  44. package/dist/v1/schema/normalization/domain.js +1 -0
  45. package/dist/v1/schema/resolution/closure.js +2 -1
  46. package/dist/v1/schema/resolution/context.js +3 -1
  47. package/dist/v1/schema/rule.d.ts +29 -0
  48. package/dist/v1/schema/rule.js +415 -0
  49. package/dist/v1/schema/validation/callable.d.ts +4 -1
  50. package/dist/v1/schema/validation/callable.js +10 -1
  51. package/dist/v1/schema/validation/class.js +36 -44
  52. package/dist/v1/schema/validation/core.js +3 -3
  53. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  54. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  55. package/dist/v1/schema/validation/domain.structure.d.ts +4018 -0
  56. package/dist/v1/schema/validation/domain.structure.js +341 -151
  57. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  58. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  59. package/dist/v1/schema/validation/policy/references.js +5 -21
  60. package/dist/v1/schema/validation/policy/uses.js +0 -3
  61. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  62. package/dist/v1/schema/validation/value-api.js +10 -1
  63. package/dist/v1/schema/validation/value.js +3 -3
  64. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  65. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  66. package/package.json +3 -1
@@ -1,7 +1,8 @@
1
1
  import { acceptDefinitionRef, } from './definition-ref.js';
2
2
  import { DOMAIN_ORIGIN_PATTERN } from './patterns.js';
3
3
  const DEFINITION_KEY = /^(?:class|function|policy|view)\.([A-Za-z][A-Za-z0-9_]*)$/u;
4
- const CORE_KEY = /^core\.([a-z][a-z0-9-]{0,99})$/u;
4
+ // The slug is the whole rest of the coordinate, its own `.` included: no member follows a Core key.
5
+ const CORE_KEY = /^core\.((?=.{1,100}$)[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*)$/u;
5
6
  const MEMBER_KEY = /^class\.([A-Za-z][A-Za-z0-9_]*)\.(property|method)\.([A-Za-z][A-Za-z0-9_]*)$/u;
6
7
  /** The keys of frozen Definition coordinates, derived once each. */
7
8
  const derived = new WeakMap();
@@ -3,6 +3,7 @@ export type DomainOrigin = string;
3
3
  export declare const DOMAIN_ORIGIN_PATTERN: RegExp;
4
4
  export declare const MEMBER_NAME_PATTERN: RegExp;
5
5
  export declare const DEPENDENCY_ALIAS_PATTERN: RegExp;
6
+ /** Labels of `[a-z][a-z0-9-]*` joined by `.`, 100 code points at most: `graph.reader`. */
6
7
  export declare const CORE_SLUG_PATTERN: RegExp;
7
8
  export declare const DOMAIN_REVISION_PATTERN: RegExp;
8
9
  export declare const PROPERTY_KEY_PATTERN: RegExp;
@@ -1,7 +1,8 @@
1
1
  export const DOMAIN_ORIGIN_PATTERN = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u;
2
2
  export const MEMBER_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/u;
3
3
  export const DEPENDENCY_ALIAS_PATTERN = /^[a-z][A-Za-z0-9]*$/u;
4
- export const CORE_SLUG_PATTERN = /^[a-z][a-z0-9-]{0,99}$/u;
4
+ /** Labels of `[a-z][a-z0-9-]*` joined by `.`, 100 code points at most: `graph.reader`. */
5
+ export const CORE_SLUG_PATTERN = /^(?=.{1,100}$)[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/u;
5
6
  export const DOMAIN_REVISION_PATTERN = /^sha256:[0-9a-f]{64}$/u;
6
7
  const DOMAIN_REVISION_FULL_MATCH = 'sha256:[0-9a-f]{64}';
7
8
  const DOMAIN_ORIGIN_FULL_MATCH = '([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?';
@@ -1,5 +1,5 @@
1
1
  import type { z } from 'zod';
2
- import type { AutoEvent, AutoSet, PropertyImmutability } from '../../language/class/model/property.js';
2
+ import type { AutoEvent, AutoSet, PropertyAuto, 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';
@@ -31,10 +31,7 @@ export interface AutoProperty<out PropertyValue extends Property = Property> {
31
31
  readonly [AUTHORING]: 'auto';
32
32
  readonly [AUTO_INPUT]: {
33
33
  readonly property: PropertyValue;
34
- readonly options: {
35
- readonly on: readonly AutoEvent[];
36
- readonly set: AutoSet;
37
- };
34
+ readonly options: PropertyAuto;
38
35
  };
39
36
  }
40
37
  type PropertyAuthoringInput<Value extends Property> = Value extends Property<infer Schema, infer Options, infer Machine> ? {
@@ -45,10 +42,7 @@ type PropertyAuthoringInput<Value extends Property> = Value extends Property<inf
45
42
  export declare function propertyAuthoringInput<const Value extends Property>(value: Value): PropertyAuthoringInput<Value>;
46
43
  export declare function autoPropertyAuthoringInput<const Value extends AutoProperty>(value: Value): Value extends AutoProperty<infer Property> ? {
47
44
  readonly property: Property;
48
- readonly options: {
49
- readonly on: readonly AutoEvent[];
50
- readonly set: AutoSet;
51
- };
45
+ readonly options: PropertyAuto;
52
46
  } : never;
53
47
  export type PropertyFieldInput = ValueSchemaInput | Property | AutoProperty;
54
48
  export type PropertyRecordInput = Readonly<Record<string, PropertyFieldInput>>;
@@ -59,6 +59,7 @@ export function auto(property, options) {
59
59
  return authoringValue('auto', {
60
60
  [AUTO_INPUT]: {
61
61
  property,
62
+ // `events` holds at least the one authored event.
62
63
  options: { on: Object.freeze(events), set: options.set },
63
64
  },
64
65
  });
@@ -48,6 +48,7 @@ export class V1DefinitionCompiler {
48
48
  }
49
49
  edgeClass(value, name, pointer) {
50
50
  const input = classConfiguration(value);
51
+ // The authoring types give an Edge no State Property; the structure refuses an erased one.
51
52
  const facet = this.values.properties(input.properties, `${pointer}/properties`);
52
53
  const contract = (() => {
53
54
  if (value.orientation === 'directed') {
@@ -63,7 +64,9 @@ export class V1DefinitionCompiler {
63
64
  // against the Class's effective Properties, once every Class is lowered.
64
65
  ...(directed.unique === undefined
65
66
  ? {}
66
- : { unique: directed.unique }),
67
+ : {
68
+ unique: directed.unique,
69
+ }),
67
70
  };
68
71
  }
69
72
  const undirected = classConfiguration(value);
@@ -135,6 +138,7 @@ export class V1DefinitionCompiler {
135
138
  endpointBase(value, pointer) {
136
139
  return {
137
140
  role: value.as,
141
+ // An empty list stays as authored: the structure refuses it when it judges the candidate.
138
142
  accepts: value.accepts
139
143
  .map((entry, index) => this.context.resolveNodeClass(entry, `${pointer}/accepts/${index}`))
140
144
  .sort(compareDefinitionRefs),
@@ -14,6 +14,7 @@ export class V1PolicyCompiler {
14
14
  const input = policyDeclaration(value);
15
15
  const ref = { origin: this.context.origin, kind: 'policy', name };
16
16
  if ('operator' in input) {
17
+ // One ref for each Policy of a non-empty composition.
17
18
  const refs = input.policies.map((policy, index) => this.context.resolvePolicy(policy, `${pointer}/${input.operator}/${index}`));
18
19
  return {
19
20
  ref,
@@ -86,10 +86,11 @@ export function createPatternContext(options) {
86
86
  });
87
87
  Object.freeze(node);
88
88
  const where = build({ node });
89
- if (nodes.length === 0) {
89
+ const [first, ...rest] = nodes;
90
+ if (first === undefined) {
90
91
  fail('BUILDER_POLICY_EMPTY_EXISTS', `${options.pointer}/match/exists/nodes`, 'exists(...) must declare at least one variable Node.');
91
92
  }
92
- return { exists: { nodes, where } };
93
+ return { exists: { nodes: [first, ...rest], where } };
93
94
  },
94
95
  allOf(...expressions) {
95
96
  nonEmpty(expressions, `${options.pointer}/match/allOf`);
@@ -7,7 +7,7 @@ import { canonicalDomainSchemaAfterValidation } from '../schema/canonical/revisi
7
7
  import { revisionOfDomainSchema } from '../schema/canonical/revision.js';
8
8
  import { MemoryDomainSchemaLookup } from '../schema/dependency/lookup.js';
9
9
  import { SchemaError } from '../schema/diagnostic.js';
10
- import { BundleError, diagnostic } from './diagnostic.js';
10
+ import { BundleError, diagnostic, schemaDiagnosticAt } from './diagnostic.js';
11
11
  import { asDomainSchemaBundle, } from './model.js';
12
12
  import { parseDomainSchemaBundleStructure } from './structure.js';
13
13
  export function acceptDomainSchemaBundle(input) {
@@ -99,7 +99,7 @@ function acceptBundleValue(input) {
99
99
  catch (error) {
100
100
  if (!(error instanceof SchemaError))
101
101
  throw error;
102
- throw new BundleError(error.diagnostics.map((item) => diagnostic(item.code, `${entry.pointer}${item.pointer}`, item.message, item.ref)));
102
+ throw new BundleError(error.diagnostics.map((item) => schemaDiagnosticAt(entry.pointer, item)));
103
103
  }
104
104
  };
105
105
  for (const entry of candidates.values()) {
@@ -4,6 +4,8 @@ export type BundleDiagnostic<Code extends BundleDiagnosticCode = BundleDiagnosti
4
4
  readonly code: Code;
5
5
  };
6
6
  export declare function diagnostic<const Code extends BundleDiagnosticCode>(code: Code, pointer: string, message: string, ref?: string): BundleDiagnostic<Code>;
7
+ /** A refusal of one bundled schema, its pointer rebased under the schema's place in the bundle. */
8
+ export declare function schemaDiagnosticAt(pointer: string, refusal: SchemaDiagnostic): BundleDiagnostic;
7
9
  export declare class BundleError extends Error {
8
10
  readonly diagnostics: readonly BundleDiagnostic[];
9
11
  constructor(diagnostics: readonly BundleDiagnostic[]);
@@ -2,6 +2,12 @@ import { formatDiagnostics, orderDiagnostics } from '../language/validation/diag
2
2
  export function diagnostic(code, pointer, message, ref) {
3
3
  return Object.freeze({ code, pointer, message, ...(ref === undefined ? {} : { ref }) });
4
4
  }
5
+ /** A refusal of one bundled schema, its pointer rebased under the schema's place in the bundle. */
6
+ export function schemaDiagnosticAt(pointer, refusal) {
7
+ // Acceptance refuses a document with Schema codes only, never with comparison request codes.
8
+ const code = refusal.code;
9
+ return Object.freeze({ ...refusal, code, pointer: `${pointer}${refusal.pointer}` });
10
+ }
5
11
  export class BundleError extends Error {
6
12
  diagnostics;
7
13
  constructor(diagnostics) {
@@ -1,7 +1,7 @@
1
1
  import { escapePointerToken } from '../language/value/syntax/pointer.js';
2
2
  import { SchemaError } from '../schema/diagnostic.js';
3
3
  import { routeDomainSchema } from '../schema/validation/domain.route.js';
4
- import { BundleError, diagnostic } from './diagnostic.js';
4
+ import { BundleError, diagnostic, schemaDiagnosticAt } from './diagnostic.js';
5
5
  import { DOMAIN_BUNDLE_FORMAT, DOMAIN_BUNDLE_VERSION } from './model.js';
6
6
  export function parseDomainSchemaBundleStructure(input) {
7
7
  if (!isObject(input)) {
@@ -57,7 +57,7 @@ function routeAt(value, pointer, diagnostics) {
57
57
  catch (error) {
58
58
  if (!(error instanceof SchemaError))
59
59
  throw error;
60
- diagnostics.push(...error.diagnostics.map((entry) => diagnostic(entry.code, `${pointer}${entry.pointer}`, entry.message, entry.ref)));
60
+ diagnostics.push(...error.diagnostics.map((entry) => schemaDiagnosticAt(pointer, entry)));
61
61
  return undefined;
62
62
  }
63
63
  }
@@ -14,6 +14,9 @@ export interface CallableValidation {
14
14
  validateInputField(field: string, input: unknown): readonly Diagnostic[];
15
15
  validateOutput(output: unknown): readonly Diagnostic[];
16
16
  }
17
- /** Build the one Domain-level router shared by source resolution and compiled loading. */
17
+ /**
18
+ * Build the one Domain-level router shared by source resolution and compiled loading. The Domain
19
+ * built from the same entries asks only for the Properties and Callables they declare.
20
+ */
18
21
  export declare function createDomainValidation(properties: ReadonlyMap<PropertyKey, PropertyValidation>, callables: ReadonlyMap<Key.Callable, CallableValidation>): DomainValidation;
19
22
  export declare function unknownInputField(field: string): readonly Diagnostic[];
@@ -1,34 +1,30 @@
1
- /** Build the one Domain-level router shared by source resolution and compiled loading. */
1
+ /**
2
+ * Build the one Domain-level router shared by source resolution and compiled loading. The Domain
3
+ * built from the same entries asks only for the Properties and Callables they declare.
4
+ */
2
5
  export function createDomainValidation(properties, callables) {
6
+ const propertyOf = (key) => {
7
+ const target = properties.get(key);
8
+ if (target === undefined)
9
+ throw new TypeError(`Property ${key} is absent.`);
10
+ return target;
11
+ };
12
+ const callableOf = (key) => {
13
+ const target = callables.get(key);
14
+ if (target === undefined)
15
+ throw new TypeError(`Callable ${key} is absent.`);
16
+ return target;
17
+ };
3
18
  const validation = {
4
19
  propertyExists: (key) => properties.has(key),
5
- property(key, input) {
6
- const target = properties.get(key);
7
- return target === undefined ? unknownTarget('Property', key) : target.validate(input);
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
- },
20
+ property: (key, input) => propertyOf(key).validate(input),
21
+ propertyProgram: (key) => propertyOf(key).program(),
15
22
  propertyOrdering: (key) => properties.get(key)?.ordering,
16
23
  callableExists: (key) => callables.has(key),
17
- callableInput(key, input) {
18
- const target = callables.get(key);
19
- return target === undefined ? unknownTarget('Callable', key) : target.validateInput(input);
20
- },
24
+ callableInput: (key, input) => callableOf(key).validateInput(input),
21
25
  callableFields: (key) => callables.get(key)?.fields,
22
- callableInputField(key, field, input) {
23
- const target = callables.get(key);
24
- return target === undefined
25
- ? unknownTarget('Callable', key)
26
- : target.validateInputField(field, input);
27
- },
28
- callableOutput(key, output) {
29
- const target = callables.get(key);
30
- return target === undefined ? unknownTarget('Callable', key) : target.validateOutput(output);
31
- },
26
+ callableInputField: (key, field, input) => callableOf(key).validateInputField(field, input),
27
+ callableOutput: (key, output) => callableOf(key).validateOutput(output),
32
28
  };
33
29
  return Object.freeze(validation);
34
30
  }
@@ -41,12 +37,3 @@ export function unknownInputField(field) {
41
37
  }),
42
38
  ];
43
39
  }
44
- function unknownTarget(kind, key) {
45
- return [
46
- Object.freeze({
47
- code: 'DOMAIN_VALIDATION_TARGET_UNKNOWN',
48
- pointer: '',
49
- message: `${kind} ${key} is absent.`,
50
- }),
51
- ];
52
- }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.astrale.ai/dsl/v1/value-schema",
4
+ "$dynamicAnchor": "meta",
5
+ "$vocabulary": {
6
+ "https://json-schema.org/draft/2020-12/vocab/core": true,
7
+ "https://json-schema.org/draft/2020-12/vocab/applicator": true,
8
+ "https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
9
+ "https://json-schema.org/draft/2020-12/vocab/validation": true,
10
+ "https://json-schema.org/draft/2020-12/vocab/meta-data": true,
11
+ "https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
12
+ "https://json-schema.org/draft/2020-12/vocab/content": true,
13
+ "https://schemas.astrale.ai/dsl/v1/value-schema/vocabulary": true
14
+ },
15
+ "title": "Astrale V1 value schema",
16
+ "$comment": "The meta-schema of the V1 value-schema dialect: Draft 2020-12 and the required Astrale vocabulary (fullMatch, x-astrale-path, x-astrale-value-kind). Its $dynamicAnchor extends Draft 2020-12 at every subschema, not only the root. The rules of the profile beyond it are published as schema.values.RULES.",
17
+ "allOf": [{ "$ref": "https://json-schema.org/draft/2020-12/schema" }],
18
+ "properties": {
19
+ "fullMatch": { "type": "string" },
20
+ "x-astrale-path": { "type": "object" }
21
+ }
22
+ }
@@ -3,7 +3,7 @@ import type { MethodRecord } from '../../callable/model/callable.js';
3
3
  import type { ClassPolicy, NodeClassPolicy } from '../../policy/model/policy.js';
4
4
  import type { DataFacet } from './data.js';
5
5
  import type { EdgeContract } from './edge.js';
6
- import type { PropertyFacet } from './property.js';
6
+ import type { EdgePropertyDefinition, NodePropertyDefinition, PropertyFacet } from './property.js';
7
7
  export type ClassKind = 'node' | 'edge';
8
8
  /**
9
9
  * Which Classes may extend a Class: `'open'`, those of its Domain and of every Domain that depends
@@ -30,7 +30,7 @@ export type NodeUniqueTuple = readonly [PropertyKey, ...PropertyKey[]];
30
30
  * backend announces it to enforce Node keys, so a tuple is never ignored where it is not.
31
31
  */
32
32
  export type NodeUniqueForm = readonly ['property'];
33
- export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet, DataFacet {
33
+ export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet<NodePropertyDefinition>, DataFacet {
34
34
  readonly kind: 'node';
35
35
  readonly abstract: boolean;
36
36
  /** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
@@ -44,9 +44,9 @@ export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyF
44
44
  * The tuples no two Nodes of this Class and of its active concrete descendants share while every
45
45
  * Property is present; absent, none. A descendant is covered without declaring them again.
46
46
  */
47
- readonly unique?: readonly NodeUniqueTuple[];
47
+ readonly unique?: readonly [NodeUniqueTuple, ...NodeUniqueTuple[]];
48
48
  }
49
- export type EdgeClassDefinition = DefinitionBase<ClassRef> & PropertyFacet & EdgeContract & {
49
+ export type EdgeClassDefinition = DefinitionBase<ClassRef> & PropertyFacet<EdgePropertyDefinition> & EdgeContract & {
50
50
  readonly kind: 'edge';
51
51
  readonly abstract: boolean;
52
52
  /** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
@@ -8,7 +8,7 @@ export type EdgeOrientation = 'directed' | 'undirected';
8
8
  export type EdgeLifetime = 'restrict' | 'detach';
9
9
  export interface EdgeEndpointBase {
10
10
  readonly role: string;
11
- readonly accepts: readonly ClassRef[];
11
+ readonly accepts: readonly [ClassRef, ...ClassRef[]];
12
12
  /** Declared only when authored; otherwise derived (`edgeLifetimes`). */
13
13
  readonly onDelete?: EdgeLifetime;
14
14
  }
@@ -56,7 +56,7 @@ export interface DirectedEdgeContract {
56
56
  * The tuples no two Edges of this Class and of its active concrete descendants share. Absent
57
57
  * when the Class declares none, so a Class without one keeps its revision; never empty.
58
58
  */
59
- readonly unique?: readonly EdgeUniqueTuple[];
59
+ readonly unique?: readonly [EdgeUniqueTuple, ...EdgeUniqueTuple[]];
60
60
  }
61
61
  export interface UndirectedEdgeContract {
62
62
  readonly orientation: 'undirected';
@@ -1,20 +1,29 @@
1
+ import type { StateTopology } from '../../state/index.js';
2
+ import type { JsonSchema } from '../../value/model/schema.js';
1
3
  export type AutoEvent = 'create' | 'update';
2
4
  export type AutoSet = 'instant';
3
5
  /** `create`: written only by node or edge creation. `once`: written while absent, never unset. */
4
6
  export type PropertyImmutability = 'create' | 'once';
5
7
  export interface PropertyAuto {
6
- readonly on: readonly AutoEvent[];
8
+ readonly on: readonly [AutoEvent, ...AutoEvent[]];
7
9
  readonly set: AutoSet;
8
10
  }
9
- export interface PropertyDefinition {
10
- readonly schema: import('../../value/model/schema.js').JsonSchema;
11
+ /** A Property of a Node Class, which may be a State Property. */
12
+ export interface NodePropertyDefinition {
13
+ readonly schema: JsonSchema;
11
14
  readonly required: boolean;
12
15
  readonly auto?: PropertyAuto;
13
16
  readonly immutable?: PropertyImmutability;
14
17
  readonly description?: string;
15
- readonly state?: import('../../state/index.js').StateTopology;
18
+ readonly state?: StateTopology;
16
19
  }
17
- export type PropertyRecord = Readonly<Record<string, PropertyDefinition>>;
18
- export interface PropertyFacet {
19
- readonly properties: PropertyRecord;
20
+ /** A Property of an Edge Class: an Edge has no State Property. */
21
+ export interface EdgePropertyDefinition extends Omit<NodePropertyDefinition, 'state'> {
22
+ readonly state?: never;
23
+ }
24
+ /** A Property of a Class of either kind. */
25
+ export type PropertyDefinition = NodePropertyDefinition | EdgePropertyDefinition;
26
+ export type PropertyRecord<Definition extends PropertyDefinition = PropertyDefinition> = Readonly<Record<string, Definition>>;
27
+ export interface PropertyFacet<Definition extends PropertyDefinition = PropertyDefinition> {
28
+ readonly properties: PropertyRecord<Definition>;
20
29
  }
@@ -18,3 +18,8 @@ export declare const name: Readonly<{
18
18
  }>;
19
19
  /** The common source and compiled grammar for a name displayed exactly as authored. */
20
20
  export declare function isDomainNameText(value: string): boolean;
21
+ /**
22
+ * The same grammar as a portable pattern: no control character, and neither first nor last a
23
+ * character `trim` removes, spelled out since `\s` differs between regular expression engines.
24
+ */
25
+ export declare const DOMAIN_NAME_TEXT_PATTERN: RegExp;
@@ -22,3 +22,10 @@ const DOMAIN_NAME_CONTROL = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/u;
22
22
  export function isDomainNameText(value) {
23
23
  return value.trim() === value && !DOMAIN_NAME_CONTROL.test(value);
24
24
  }
25
+ /**
26
+ * The same grammar as a portable pattern: no control character, and neither first nor last a
27
+ * character `trim` removes, spelled out since `\s` differs between regular expression engines.
28
+ */
29
+ export const DOMAIN_NAME_TEXT_PATTERN =
30
+ // oxlint-disable-next-line no-control-regex
31
+ /^(?:[^\u0000-\u001f\u007f-\u009f\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff](?:[^\u0000-\u001f\u007f-\u009f\u2028\u2029]*[^\u0000-\u001f\u007f-\u009f\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff])?)?$/u;
@@ -65,20 +65,20 @@ export interface PolicyEdgePattern {
65
65
  }
66
66
  export type PolicyPatternExpression = PolicyEdgePattern | PolicySameNode<PolicyPatternTerm> | {
67
67
  readonly exists: {
68
- readonly nodes: readonly PolicyVariable[];
68
+ readonly nodes: readonly [PolicyVariable, ...PolicyVariable[]];
69
69
  readonly where: PolicyPatternExpression;
70
70
  };
71
71
  } | {
72
- readonly allOf: readonly PolicyPatternExpression[];
72
+ readonly allOf: readonly [PolicyPatternExpression, ...PolicyPatternExpression[]];
73
73
  } | {
74
- readonly anyOf: readonly PolicyPatternExpression[];
74
+ readonly anyOf: readonly [PolicyPatternExpression, ...PolicyPatternExpression[]];
75
75
  };
76
76
  export type PolicyExpression = {
77
77
  readonly match: PolicyPatternExpression;
78
78
  } | {
79
- readonly allOf: readonly PolicyRef[];
79
+ readonly allOf: readonly [PolicyRef, ...PolicyRef[]];
80
80
  } | {
81
- readonly anyOf: readonly PolicyRef[];
81
+ readonly anyOf: readonly [PolicyRef, ...PolicyRef[]];
82
82
  };
83
83
  export interface PolicyDefinition extends DefinitionBase<PolicyRef> {
84
84
  readonly expression: PolicyExpression;
@@ -6,9 +6,9 @@ export function normalizePolicyExpression(value) {
6
6
  return { match: normalizePolicyPattern(value.match) };
7
7
  }
8
8
  if ('allOf' in value) {
9
- return { allOf: uniqueRefs(value.allOf) };
9
+ return { allOf: operands(uniqueRefs(value.allOf)) };
10
10
  }
11
- return { anyOf: uniqueRefs(value.anyOf) };
11
+ return { anyOf: operands(uniqueRefs(value.anyOf)) };
12
12
  }
13
13
  /**
14
14
  * Passes after the first, while one still changes the Pattern. A pass numbers variables by the
@@ -66,8 +66,7 @@ function unwrapSingleOperands(value) {
66
66
  }));
67
67
  if (children.length === 1)
68
68
  return children[0];
69
- const operands = policyCheckComposition(children);
70
- return all ? { allOf: operands } : { anyOf: operands };
69
+ return all ? { allOf: operands(children) } : { anyOf: operands(children) };
71
70
  }
72
71
  export function normalizePolicyChecks(value) {
73
72
  if ('check' in value)
@@ -79,17 +78,19 @@ export function normalizePolicyChecks(value) {
79
78
  const normalized = normalizePolicyChecks(child);
80
79
  return 'allOf' in normalized ? normalized.allOf : [normalized];
81
80
  });
82
- return { allOf: policyCheckComposition(uniqueJson(children)) };
81
+ return { allOf: operands(uniqueJson(children)) };
83
82
  }
84
83
  const children = value.anyOf.flatMap((child) => {
85
84
  const normalized = normalizePolicyChecks(child);
86
85
  return 'anyOf' in normalized ? normalized.anyOf : [normalized];
87
86
  });
88
- return { anyOf: policyCheckComposition(uniqueJson(children)) };
87
+ return { anyOf: operands(uniqueJson(children)) };
89
88
  }
90
- function policyCheckComposition(values) {
91
- // Structural routing may carry an invalid empty array into semantic validation;
92
- // validateChecks reports it before the DomainSchema can be accepted.
89
+ /**
90
+ * The operands of a non-empty list, mapped, flattened, deduplicated or sorted one by one: still at
91
+ * least one. The structure and compiled admission refuse an empty list.
92
+ */
93
+ function operands(values) {
93
94
  return values;
94
95
  }
95
96
  function normalizePatternNode(value) {
@@ -106,7 +107,7 @@ function normalizePatternNode(value) {
106
107
  if ('exists' in value) {
107
108
  return {
108
109
  exists: {
109
- nodes: [...value.exists.nodes].sort((left, right) => left.variable.id - right.variable.id),
110
+ nodes: operands([...value.exists.nodes].sort((left, right) => left.variable.id - right.variable.id)),
110
111
  where: normalizePatternNode(value.exists.where),
111
112
  },
112
113
  };
@@ -116,13 +117,13 @@ function normalizePatternNode(value) {
116
117
  const normalized = normalizePatternNode(child);
117
118
  return 'allOf' in normalized ? normalized.allOf : [normalized];
118
119
  });
119
- return { allOf: uniqueJson(children) };
120
+ return { allOf: operands(uniqueJson(children)) };
120
121
  }
121
122
  const children = value.anyOf.flatMap((child) => {
122
123
  const normalized = normalizePatternNode(child);
123
124
  return 'anyOf' in normalized ? normalized.anyOf : [normalized];
124
125
  });
125
- return { anyOf: uniqueJson(children) };
126
+ return { anyOf: operands(uniqueJson(children)) };
126
127
  }
127
128
  function canonicalVariableIds(value) {
128
129
  const declarations = new Map();
@@ -297,7 +298,7 @@ function rewriteVariables(value, ids) {
297
298
  if ('exists' in value) {
298
299
  return {
299
300
  exists: {
300
- nodes: value.exists.nodes.map((node) => {
301
+ nodes: operands(value.exists.nodes.map((node) => {
301
302
  const variable = {
302
303
  kind: 'variable',
303
304
  id: ids.get(node.variable.id) ?? node.variable.id,
@@ -305,14 +306,14 @@ function rewriteVariables(value, ids) {
305
306
  return 'class' in node
306
307
  ? { variable, class: node.class }
307
308
  : { variable, selector: node.selector };
308
- }),
309
+ })),
309
310
  where: rewriteVariables(value.exists.where, ids),
310
311
  },
311
312
  };
312
313
  }
313
314
  if ('allOf' in value)
314
- return { allOf: value.allOf.map((child) => rewriteVariables(child, ids)) };
315
- return { anyOf: value.anyOf.map((child) => rewriteVariables(child, ids)) };
315
+ return { allOf: operands(value.allOf.map((child) => rewriteVariables(child, ids))) };
316
+ return { anyOf: operands(value.anyOf.map((child) => rewriteVariables(child, ids))) };
316
317
  }
317
318
  function selectorSignature(node) {
318
319
  const selector = policyVariableSelector(node);
@@ -1,4 +1,4 @@
1
- export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | 'DOMAIN_VALIDATION_TARGET_UNKNOWN' | 'VALUE_SCHEMA_INSTANCE_INVALID' | 'VS_DIALECT' | 'VS_DYNAMIC_FORBIDDEN' | 'VS_KEYWORD_CLOSED' | 'VS_META_SCHEMA' | 'VS_NUMBER_DOMAIN' | 'VS_PATH_EXTENSION' | 'VS_RECURSION_PRODUCTIVE' | 'VS_REFERENCE_FORM' | 'VS_REFERENCE_RESOLUTION' | 'VS_REGEX_PORTABLE' | 'VS_RESOURCE_ID' | 'VS_VALUE_KIND' | 'VS_VALUE_KIND_JSON';
1
+ export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | 'VALUE_SCHEMA_INSTANCE_INVALID' | 'VS_DIALECT' | 'VS_DYNAMIC_FORBIDDEN' | 'VS_KEYWORD_CLOSED' | 'VS_META_SCHEMA' | 'VS_NUMBER_DOMAIN' | 'VS_PATH_EXTENSION' | 'VS_RECURSION_PRODUCTIVE' | 'VS_REFERENCE_FORM' | 'VS_REFERENCE_RESOLUTION' | 'VS_REGEX_PORTABLE' | 'VS_RESOURCE_ID' | 'VS_VALUE_KIND' | 'VS_VALUE_KIND_JSON';
2
2
  export interface Diagnostic<out Code extends DiagnosticCode = DiagnosticCode> {
3
3
  readonly code: Code;
4
4
  readonly pointer: string;
@@ -0,0 +1,26 @@
1
+ import type { DiagnosticCode } from './diagnostic.js';
2
+ /**
3
+ * What a rule reads to decide. `schema`: a value schema alone, with its place in the declaration
4
+ * that holds it (a Property, a callable input or output). `context`: a value schema and what its
5
+ * Domain can reach, the Node Classes of the Domain and of its direct dependencies and the exact
6
+ * dependency closure. `value`: a value, under an accepted schema or a contract of the Domain.
7
+ * `warning`: a value schema, which the rule reports without refusing it.
8
+ */
9
+ export type ValueRuleStage = 'schema' | 'context' | 'value' | 'warning';
10
+ /** The rule one value diagnostic code reports. */
11
+ export interface ValueRule {
12
+ readonly stage: ValueRuleStage;
13
+ /**
14
+ * The positions the rule constrains, which its diagnostics point at, relative to the value
15
+ * schema, the value or the Domain input it checks: JSON Pointer templates in which a `{name}`
16
+ * token stands for one token of the kind it names and `{/path*}` for any number of tokens.
17
+ */
18
+ readonly positions: readonly [string, ...string[]];
19
+ /** The rule, in one plain sentence. */
20
+ readonly statement: string;
21
+ }
22
+ /**
23
+ * Every value diagnostic code with the rule it reports. A code added to the language does not
24
+ * compile until it is described here.
25
+ */
26
+ export declare const VALUE_RULES: Readonly<Record<DiagnosticCode, ValueRule>>;