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

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 (103) hide show
  1. package/dist/v1/addressing/domain-dependency.d.ts +0 -6
  2. package/dist/v1/addressing/domain-dependency.js +1 -6
  3. package/dist/v1/addressing/key.js +2 -1
  4. package/dist/v1/addressing/patterns.d.ts +1 -0
  5. package/dist/v1/addressing/patterns.js +2 -1
  6. package/dist/v1/builder/class/authoring.d.ts +43 -0
  7. package/dist/v1/builder/class/property.d.ts +3 -9
  8. package/dist/v1/builder/class/property.js +1 -0
  9. package/dist/v1/builder/index.d.ts +1 -1
  10. package/dist/v1/builder/lowering/class.js +5 -1
  11. package/dist/v1/builder/lowering/policy.js +2 -0
  12. package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
  13. package/dist/v1/builder/lowering/schema/context.js +22 -0
  14. package/dist/v1/builder/policy/authoring.d.ts +84 -4
  15. package/dist/v1/builder/policy/authoring.js +44 -14
  16. package/dist/v1/builder/schema/projection/class.d.ts +116 -14
  17. package/dist/v1/bundle/codec.js +9 -9
  18. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  19. package/dist/v1/bundle/diagnostic.js +6 -0
  20. package/dist/v1/bundle/structure.js +2 -2
  21. package/dist/v1/compiled/admission/accept.js +3 -3
  22. package/dist/v1/compiled/admission/closure.js +2 -2
  23. package/dist/v1/compiled/admission/semantics/core.js +3 -7
  24. package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
  25. package/dist/v1/domain/validation/create.d.ts +4 -1
  26. package/dist/v1/domain/validation/create.js +21 -34
  27. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  28. package/dist/v1/language/class/model/class.d.ts +4 -4
  29. package/dist/v1/language/class/model/edge.d.ts +2 -2
  30. package/dist/v1/language/class/model/property.d.ts +16 -7
  31. package/dist/v1/language/core/facts.d.ts +17 -4
  32. package/dist/v1/language/core/facts.js +26 -5
  33. package/dist/v1/language/definition/metadata.d.ts +5 -0
  34. package/dist/v1/language/definition/metadata.js +7 -0
  35. package/dist/v1/language/limits.d.ts +64 -0
  36. package/dist/v1/language/limits.js +60 -0
  37. package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
  38. package/dist/v1/language/policy/analysis/budgets.js +30 -20
  39. package/dist/v1/language/policy/analysis/checks.js +3 -2
  40. package/dist/v1/language/policy/analysis/typing.js +4 -3
  41. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  42. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  43. package/dist/v1/language/validation/diagnostic.d.ts +1 -1
  44. package/dist/v1/language/validation/rule.d.ts +29 -0
  45. package/dist/v1/language/validation/rule.js +120 -0
  46. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  47. package/dist/v1/language/value/profile/rules.js +13 -37
  48. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  49. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  50. package/dist/v1/language/value/reference/registry.index.js +2 -1
  51. package/dist/v1/language/value/regexp/ast.js +6 -7
  52. package/dist/v1/language/value/regexp/compile.d.ts +5 -11
  53. package/dist/v1/language/value/regexp/compile.js +14 -12
  54. package/dist/v1/language/value/regexp/syntax.js +3 -2
  55. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  56. package/dist/v1/language/value/syntax/keywords.js +4 -20
  57. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  58. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  59. package/dist/v1/schema/admission/accept.js +3 -14
  60. package/dist/v1/schema/admission/callable.d.ts +21 -0
  61. package/dist/v1/schema/admission/callable.js +96 -0
  62. package/dist/v1/schema/analysis/class.js +33 -15
  63. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  64. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  65. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  66. package/dist/v1/schema/diagnostic.d.ts +17 -3
  67. package/dist/v1/schema/diagnostic.js +11 -0
  68. package/dist/v1/schema/index.d.ts +10 -1
  69. package/dist/v1/schema/index.js +4 -0
  70. package/dist/v1/schema/model/schema.d.ts +19 -1
  71. package/dist/v1/schema/normalization/domain.d.ts +2 -0
  72. package/dist/v1/schema/normalization/domain.js +2 -1
  73. package/dist/v1/schema/resolution/closure.js +2 -1
  74. package/dist/v1/schema/resolution/context.js +10 -8
  75. package/dist/v1/schema/resolution/domain.d.ts +5 -0
  76. package/dist/v1/schema/resolution/domain.js +1 -1
  77. package/dist/v1/schema/rule.d.ts +32 -0
  78. package/dist/v1/schema/rule.js +428 -0
  79. package/dist/v1/schema/validation/callable.d.ts +35 -2
  80. package/dist/v1/schema/validation/callable.js +51 -8
  81. package/dist/v1/schema/validation/class.js +36 -44
  82. package/dist/v1/schema/validation/core.js +19 -13
  83. package/dist/v1/schema/validation/domain.js +2 -2
  84. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  85. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  86. package/dist/v1/schema/validation/domain.route.d.ts +6 -1
  87. package/dist/v1/schema/validation/domain.route.js +18 -0
  88. package/dist/v1/schema/validation/domain.structure.d.ts +4020 -0
  89. package/dist/v1/schema/validation/domain.structure.js +359 -153
  90. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  91. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  92. package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
  93. package/dist/v1/schema/validation/policy/budgets.js +89 -16
  94. package/dist/v1/schema/validation/policy/references.js +6 -21
  95. package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
  96. package/dist/v1/schema/validation/policy/uses.js +28 -51
  97. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  98. package/dist/v1/schema/validation/value-api.js +10 -1
  99. package/dist/v1/schema/validation/value.d.ts +3 -0
  100. package/dist/v1/schema/validation/value.js +6 -12
  101. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  102. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  103. package/package.json +3 -1
@@ -10,9 +10,3 @@ export type DomainDependency = JsonObject & {
10
10
  export type DependencyAlias = string;
11
11
  /** Exact direct dependency pins keyed by their authored aliases. */
12
12
  export type DomainDependencyMap = Readonly<Record<DependencyAlias, DomainDependency>>;
13
- /** Representation-neutral admission budgets shared by source and compiled closures. */
14
- export declare const dependencyLimits: Readonly<{
15
- direct: 32;
16
- closure: 256;
17
- depth: 32;
18
- }>;
@@ -1,6 +1 @@
1
- /** Representation-neutral admission budgets shared by source and compiled closures. */
2
- export const dependencyLimits = Object.freeze({
3
- direct: 32,
4
- closure: 256,
5
- depth: 32,
6
- });
1
+ export {};
@@ -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,6 @@
1
1
  import type { Value as PortableValue } from '@astrale-os/kernel-dsl/value';
2
2
  import type { z } from 'zod';
3
+ import type { ClassRef } from '../../addressing/index.js';
3
4
  import type { ResolvedClass, ResolvedProperty } from '../../language/class/resolved.js';
4
5
  import type { StateTopologyOf } from '../../language/state/topology.js';
5
6
  import type { BlobReceipt, BlobRef, BlobSchema } from '../../language/value/model/blob.js';
@@ -82,6 +83,44 @@ type ClassDepthOfInput<Input> = Input extends {
82
83
  } ? Parents extends readonly [] ? 0 : Increment<ClassDepthOf<Parents[number]>> : 0;
83
84
  type CompactReference<Value> = Value extends () => infer Resolved ? () => CompactReference<Resolved> : Value extends ResolvedClass ? ClassProjectionOfResolved<Value> : Value extends Class ? ClassReferenceProjectionOf<Value> : Value;
84
85
  type CompactReferences<Values extends readonly unknown[]> = Values extends readonly [] ? readonly [] : Values extends readonly [infer Value, ...infer Rest] ? readonly [CompactReference<Value>, ...CompactReferences<Rest>] : readonly CompactReference<Values[number]>[];
86
+ /**
87
+ * A Class an endpoint accepts, as its projection keeps it: the exact ref of a resolved Class, the
88
+ * mark of an authored one, which the Schema names, or the ref as given. A lazy reference stays lazy.
89
+ */
90
+ type CompactAccepted<Value> = Value extends () => infer Resolved ? () => CompactAccepted<Resolved> : Value extends ResolvedClass ? ExactClassRefOf<Value> : Value extends NodeClass<infer Config> ? AcceptedClassMark<Config> : Value;
91
+ /**
92
+ * What tells an authored Node Class from the other Classes of its Schema in an endpoint's type:
93
+ * its abstractness, its depth and the names of its effective members. Names alone keep the Edge
94
+ * handle small enough to print in a declaration file; Classes alike in all of these are the same
95
+ * accepted Class to the types, never to the Schema. `schema/projection/class.ts` names the Classes
96
+ * of a Schema and of its direct dependencies that bear the mark, since either may own the handle.
97
+ */
98
+ type AcceptedClassMark<Config> = {
99
+ readonly kind: 'node';
100
+ readonly abstract: Config extends {
101
+ readonly abstract: infer Abstract;
102
+ } ? Abstract : never;
103
+ readonly depth: Config extends {
104
+ readonly depth: infer Depth;
105
+ } ? Depth : never;
106
+ readonly properties: Config extends {
107
+ readonly effectiveProperties: infer Properties;
108
+ } ? Properties extends {
109
+ readonly name: infer Name;
110
+ } ? Name : never : never;
111
+ readonly methods: Config extends {
112
+ readonly effectiveInstanceMethods: infer Instance;
113
+ readonly effectiveStaticMethods: infer Static;
114
+ } ? Instance | Static extends infer Method ? Method extends {
115
+ readonly name: infer Name;
116
+ } ? Name : never : never : never;
117
+ };
118
+ /** The ref of a resolved Class, exact while its key is. */
119
+ type ExactClassRefOf<Value extends ResolvedClass> = Value['key'] extends `${infer Origin}:class.${infer Name}` ? string extends Origin ? ClassRef : string extends Name ? ClassRef : ClassRef<Name> & {
120
+ readonly origin: Origin;
121
+ } : ClassRef;
122
+ /** The Classes one endpoint accepts, in any order: the runtime list is canonically sorted. */
123
+ type CompactAccepts<Values> = Values extends readonly (infer Value)[] ? readonly CompactAccepted<Value>[] : readonly never[];
85
124
  type CompactParentsOf<Input> = Input extends {
86
125
  readonly extends?: infer Values extends readonly unknown[];
87
126
  } ? 'extends' extends keyof Input ? CompactReferences<Values> : readonly [] : readonly [];
@@ -114,10 +153,12 @@ type CompactDirectedInput<Input extends Omit<DirectedEdgeInput, 'orientation'>>
114
153
  } ? NonNullable<Value> : never : never>;
115
154
  readonly source: {
116
155
  readonly as: Input['source']['as'];
156
+ readonly accepts: CompactAccepts<Input['source']['accepts']>;
117
157
  readonly outgoing: Input['source']['outgoing'];
118
158
  };
119
159
  readonly target: {
120
160
  readonly as: Input['target']['as'];
161
+ readonly accepts: CompactAccepts<Input['target']['accepts']>;
121
162
  readonly incoming: Input['target']['incoming'];
122
163
  };
123
164
  readonly constraints: Input extends {
@@ -137,10 +178,12 @@ type CompactUndirectedInput<Input extends Omit<UndirectedEdgeInput, 'orientation
137
178
  readonly endpoints: readonly [
138
179
  {
139
180
  readonly as: Input['endpoints'][0]['as'];
181
+ readonly accepts: CompactAccepts<Input['endpoints'][0]['accepts']>;
140
182
  readonly incident: Input['endpoints'][0]['incident'];
141
183
  },
142
184
  {
143
185
  readonly as: Input['endpoints'][1]['as'];
186
+ readonly accepts: CompactAccepts<Input['endpoints'][1]['accepts']>;
144
187
  readonly incident: Input['endpoints'][1]['incident'];
145
188
  }
146
189
  ];
@@ -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
  });
@@ -19,7 +19,7 @@ export type { ClassReferenceInput as ClassReference, ClassRuleBuilder, ClassRule
19
19
  export type { AuthoredPropertyProjection, AuthoredMethodProjection, ClassIdentityProjection, ClassOwnerProjection, ClassReferenceProjection, } from './class/projection.js';
20
20
  export type { PathAuthoring } from './value/path.js';
21
21
  export type { Lazy } from './schema/lazy.js';
22
- export type { MatchPolicyInput, Policy, PolicyBuilder as PolicyFactory, PolicyEdgeInput, PolicyExistsContext, PolicyInput, PolicyNodeBinder, PolicyPatternContext, } from './policy/authoring.js';
22
+ export type { EdgeRolesOf, MatchPolicyInput, Policy, PolicyBuilder as PolicyFactory, PolicyEdgeClassInput, PolicyEdgeEnds, PolicyEdgeInput, PolicyEdgeOptions, PolicyEdgeRepeat, PolicyExistsContext, PolicyInput, PolicyNodeBinder, PolicyPatternContext, } from './policy/authoring.js';
23
23
  export type { AutoOptions, AutoProperty, Property, PropertyFieldInput as PropertyField, PropertyOptions, ValueSchemaInput, } from './class/property.js';
24
24
  export type { CorePropertyInput } from './schema/projection.js';
25
25
  export type { ClassProjection, FunctionProjection, MethodProjection, PropertyProjection, } from './schema/projection.js';
@@ -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,
@@ -23,6 +24,7 @@ export class V1PolicyCompiler {
23
24
  const match = input.match(createPatternContext({
24
25
  resolveObject: (member, refPointer) => this.context.resolvePolicyObject(member, refPointer),
25
26
  resolveEdgeClass: (definition, refPointer) => this.context.resolveEdgeClass(definition, refPointer),
27
+ edgeRoles: (ref) => this.context.edgeRoles(ref),
26
28
  resolveNodeClass: (definition, refPointer) => this.context.resolveNodeClass(definition, refPointer),
27
29
  pointer,
28
30
  }));
@@ -34,6 +34,11 @@ export declare class BuilderContext {
34
34
  resolveParent(input: ClassReferenceInput, kind: ClassKind, pointer: string): ClassRef;
35
35
  resolveNodeClass(input: ClassReferenceInput, pointer: string): ClassRef;
36
36
  resolveEdgeClass(input: ClassReferenceInput, pointer: string): ClassRef;
37
+ /**
38
+ * The roles of a resolved Edge Class's source and target, or of an undirected Class's two
39
+ * endpoints in their stored order, which is the authored one.
40
+ */
41
+ edgeRoles(ref: ClassRef): readonly [source: string, target: string];
37
42
  resolvePolicy(input: Lazy<unknown>, pointer: string): PolicyRef;
38
43
  resolvePolicyObject(input: PolicyObjectInput | PolicyObjectRef, pointer: string): PolicyObjectRef;
39
44
  /** A Core fact belongs to the Domain that declares it, so a dependency endpoint never resolves. */
@@ -8,6 +8,7 @@ import { escapePointerToken } from '../../../language/value/syntax/pointer.js';
8
8
  import { revisionOfDomainSchema } from '../../../schema/canonical/revision.js';
9
9
  import { requireDomainClosure } from '../../../schema/resolution/closure.js';
10
10
  import { collectUsedForeignOrigins } from '../../../schema/resolution/origins.js';
11
+ import { classConfiguration } from '../../class/authoring.js';
11
12
  import { fail } from '../../schema/diagnostic.js';
12
13
  import { resolveLazy } from '../../schema/lazy.js';
13
14
  import { OwnershipCapture } from './ownership.js';
@@ -16,6 +17,7 @@ export class BuilderContext {
16
17
  ownership = new OwnershipCapture();
17
18
  localDefinitions = new Map();
18
19
  localClassKinds = new Map();
20
+ #localClasses = new Map();
19
21
  policies = new Map();
20
22
  policyObjects = new Map();
21
23
  policyObjectRefs = new Set();
@@ -49,6 +51,7 @@ export class BuilderContext {
49
51
  }
50
52
  this.localDefinitions.set(key, ref);
51
53
  this.localClassKinds.set(key, value.kind);
54
+ this.#localClasses.set(key, value);
52
55
  this.ownership.captureDefinition(value, ref, pointer);
53
56
  this.capturePolicyObject(value, ref);
54
57
  }
@@ -109,6 +112,25 @@ export class BuilderContext {
109
112
  resolveEdgeClass(input, pointer) {
110
113
  return this.resolveClass(input, 'edge', pointer);
111
114
  }
115
+ /**
116
+ * The roles of a resolved Edge Class's source and target, or of an undirected Class's two
117
+ * endpoints in their stored order, which is the authored one.
118
+ */
119
+ edgeRoles(ref) {
120
+ if (ref.origin === this.origin) {
121
+ const value = this.#localClasses.get(definitionRefIndex(ref));
122
+ if (value.orientation === 'directed') {
123
+ const input = classConfiguration(value);
124
+ return [input.source.as, input.target.as];
125
+ }
126
+ const input = classConfiguration(value);
127
+ return [input.endpoints[0].as, input.endpoints[1].as];
128
+ }
129
+ const definition = this.#dependencies.get(ref.origin).schema.classes[ref.name];
130
+ return definition.orientation === 'directed'
131
+ ? [definition.endpoints.source.role, definition.endpoints.target.role]
132
+ : [definition.endpoints[0].role, definition.endpoints[1].role];
133
+ }
112
134
  resolvePolicy(input, pointer) {
113
135
  const value = resolveLazy(input, pointer);
114
136
  if (typeof value !== 'object' || value === null) {
@@ -1,18 +1,79 @@
1
1
  import type { ClassRef } from '../../addressing/index.js';
2
+ import type { ResolvedClass } from '../../language/class/resolved.js';
2
3
  import type { PolicyCheckExpression, PolicyPatternExpression, PolicyPatternTerm as CanonicalPolicyPatternTerm, PolicyObjectRef } from '../../language/policy/model/policy.js';
3
4
  import type { PolicyObjectInput } from '../callable/authoring.js';
5
+ import type { DirectedEdgeClass, EdgeClass, UndirectedEdgeClass } from '../class/handle.js';
4
6
  import type { EdgeClassReferenceInput, NodeClassReferenceInput } from '../class/input.js';
5
7
  import type { AuthoringValue } from '../schema/handle.js';
6
8
  import type { Lazy } from '../schema/lazy.js';
9
+ import type { ResolveLazy } from '../schema/projection/identity.js';
10
+ /**
11
+ * @deprecated Name the ends by their roles: `edge(classValue, { [role]: term }, { repeat })`
12
+ * lowers to this same pattern.
13
+ */
7
14
  export interface PolicyEdgeInput {
8
15
  readonly source: CanonicalPolicyPatternTerm;
9
16
  readonly class: EdgeClassReferenceInput;
10
17
  readonly target: CanonicalPolicyPatternTerm;
11
- readonly repeat?: {
12
- readonly min: number;
13
- readonly max: number;
14
- };
18
+ readonly repeat?: PolicyEdgeRepeat;
19
+ }
20
+ /** How many Edges of the Class a path between the two terms crosses, both bounds included. */
21
+ export interface PolicyEdgeRepeat {
22
+ readonly min: number;
23
+ readonly max: number;
24
+ }
25
+ export interface PolicyEdgeOptions {
26
+ readonly repeat?: PolicyEdgeRepeat;
15
27
  }
28
+ /** An Edge Class whose ends a Policy names by role: authored, resolved, or either lazily. */
29
+ export type PolicyEdgeClassInput = Lazy<EdgeClass | ResolvedClass<'edge'>>;
30
+ /**
31
+ * The roles of an Edge Class's two ends, as literal types: the `as` of an authored Class's ends,
32
+ * the `role` of a resolved Class's, read through a lazy reference.
33
+ */
34
+ export type EdgeRolesOf<Value> = ResolveLazy<Value> extends infer Resolved ? Resolved extends DirectedEdgeClass<infer Config> ? Config extends {
35
+ readonly source: {
36
+ readonly as: infer Source;
37
+ };
38
+ readonly target: {
39
+ readonly as: infer Target;
40
+ };
41
+ } ? Source | Target : never : Resolved extends UndirectedEdgeClass<infer Config> ? Config extends {
42
+ readonly endpoints: readonly [
43
+ {
44
+ readonly as: infer First;
45
+ },
46
+ {
47
+ readonly as: infer Second;
48
+ }
49
+ ];
50
+ } ? First | Second : never : Resolved extends {
51
+ readonly kind: 'edge';
52
+ readonly definition: {
53
+ readonly endpoints: infer Endpoints;
54
+ };
55
+ } ? Endpoints extends {
56
+ readonly source: {
57
+ readonly role: infer Source;
58
+ };
59
+ readonly target: {
60
+ readonly role: infer Target;
61
+ };
62
+ } ? Source | Target : Endpoints extends readonly [
63
+ {
64
+ readonly role: infer First;
65
+ },
66
+ {
67
+ readonly role: infer Second;
68
+ }
69
+ ] ? First | Second : never : never : never;
70
+ /**
71
+ * The two ends of an Edge, keyed by exactly the Class's two roles, both required. A Class whose
72
+ * roles the types do not know names no ends.
73
+ */
74
+ export type PolicyEdgeEnds<Value> = [EdgeRolesOf<Value>] extends [never] ? never : string extends EdgeRolesOf<Value> ? never : {
75
+ readonly [Role in EdgeRolesOf<Value> & string]: CanonicalPolicyPatternTerm;
76
+ };
16
77
  export interface PolicyNodeBinder {
17
78
  (): CanonicalPolicyPatternTerm;
18
79
  (definition: NodeClassReferenceInput): CanonicalPolicyPatternTerm;
@@ -36,6 +97,23 @@ export interface PolicyPatternContext {
36
97
  readonly object: CanonicalPolicyPatternTerm;
37
98
  readonly source: CanonicalPolicyPatternTerm;
38
99
  readonly target: CanonicalPolicyPatternTerm;
100
+ /**
101
+ * An Edge of `classValue` between two terms, each keyed by the role of its end: `{ [role]: term }`,
102
+ * the source's role written first by convention. It lowers to the pattern whose source is the
103
+ * term of the source's role and whose target is the term of the target's role. For an undirected
104
+ * Class, the first endpoint's role lowers to the source, and the roles only order the two terms:
105
+ * the pattern still matches an Edge between the two Nodes whichever end each Node holds.
106
+ *
107
+ * ```ts
108
+ * edge(project_owned_by, { owner: subject, project })
109
+ * edge(() => subtask_of, { child: a, parent: b }, { repeat: { min: 1, max: 3 } })
110
+ * ```
111
+ */
112
+ edge<ClassValue extends PolicyEdgeClassInput>(classValue: ClassValue, ends: PolicyEdgeEnds<ClassValue>, options?: PolicyEdgeOptions): PolicyPatternExpression;
113
+ /**
114
+ * @deprecated Name the ends by their roles: `edge(classValue, { [role]: term }, { repeat })`
115
+ * lowers to this same pattern.
116
+ */
39
117
  edge(input: PolicyEdgeInput): PolicyPatternExpression;
40
118
  exists(build: (context: PolicyExistsContext) => PolicyPatternExpression): PolicyPatternExpression;
41
119
  allOf(first: PolicyPatternExpression, ...rest: readonly PolicyPatternExpression[]): PolicyPatternExpression;
@@ -63,6 +141,8 @@ export declare const policy: PolicyBuilder;
63
141
  export declare function createPatternContext(options: {
64
142
  readonly resolveObject: (value: PolicyObjectInput, pointer: string) => PolicyObjectRef;
65
143
  readonly resolveEdgeClass: (value: EdgeClassReferenceInput, pointer: string) => ClassRef;
144
+ /** The roles of an Edge Class's source and target, or of an undirected Class's two endpoints. */
145
+ readonly edgeRoles: (ref: ClassRef) => readonly [source: string, target: string];
66
146
  readonly resolveNodeClass: (value: NodeClassReferenceInput, pointer: string) => ClassRef;
67
147
  readonly pointer: string;
68
148
  }): PolicyPatternContext;
@@ -1,4 +1,4 @@
1
- import { assertKnownKeys, fail } from '../schema/diagnostic.js';
1
+ import { assertKnownKeys, escapeToken, fail } from '../schema/diagnostic.js';
2
2
  import { authoringValue } from '../schema/handle.js';
3
3
  const declarations = new WeakMap();
4
4
  const compositions = new WeakSet();
@@ -31,6 +31,7 @@ export function createPatternContext(options) {
31
31
  const object = Object.freeze({ kind: 'object' });
32
32
  const source = Object.freeze({ kind: 'source' });
33
33
  const target = Object.freeze({ kind: 'target' });
34
+ const classRef = (value) => options.resolveEdgeClass(value, `${options.pointer}/match/class`);
34
35
  const context = {
35
36
  ref: (member) => ({
36
37
  kind: 'ref',
@@ -41,17 +42,36 @@ export function createPatternContext(options) {
41
42
  object,
42
43
  source,
43
44
  target,
44
- edge(input) {
45
- assertKnownKeys(input, ['source', 'class', 'target', 'repeat']);
46
- if (input.repeat !== undefined)
47
- assertKnownKeys(input.repeat, ['min', 'max']);
48
- return {
49
- source: input.source,
50
- class: options.resolveEdgeClass(input.class, `${options.pointer}/match/class`),
51
- target: input.target,
52
- ...(input.repeat === undefined ? {} : { repeat: { ...input.repeat } }),
53
- };
54
- },
45
+ edge: ((first, ends, edgeOptions) => {
46
+ const pointer = `${options.pointer}/match/edge`;
47
+ if (ends === undefined) {
48
+ if (typeof first === 'function' || first.kind === 'edge') {
49
+ fail('BUILDER_POLICY', pointer, 'edge(classValue, ends) names both ends by their roles.');
50
+ }
51
+ const input = first;
52
+ assertKnownKeys(input, ['source', 'class', 'target', 'repeat']);
53
+ if (input.repeat !== undefined)
54
+ assertKnownKeys(input.repeat, ['min', 'max']);
55
+ return edgePattern(input.source, classRef(input.class), input.target, input.repeat);
56
+ }
57
+ const ref = classRef(first);
58
+ const [sourceRole, targetRole] = options.edgeRoles(ref);
59
+ if (typeof ends !== 'object' || ends === null) {
60
+ fail('BUILDER_POLICY', pointer, 'edge(classValue, ends) names both ends by their roles.');
61
+ }
62
+ assertKnownKeys(ends, [sourceRole, targetRole], pointer);
63
+ for (const role of [sourceRole, targetRole]) {
64
+ if (!Object.hasOwn(ends, role)) {
65
+ fail('BUILDER_POLICY', `${pointer}/${escapeToken(role)}`, `edge(...) names both ends of ${ref.origin}:class.${ref.name}: "${sourceRole}" and "${targetRole}".`);
66
+ }
67
+ }
68
+ if (edgeOptions !== undefined)
69
+ assertKnownKeys(edgeOptions, ['repeat'], pointer);
70
+ if (edgeOptions?.repeat !== undefined) {
71
+ assertKnownKeys(edgeOptions.repeat, ['min', 'max'], `${pointer}/repeat`);
72
+ }
73
+ return edgePattern(ends[sourceRole], ref, ends[targetRole], edgeOptions?.repeat);
74
+ }),
55
75
  exists(build) {
56
76
  const nodes = [];
57
77
  const bind = (selector, definition) => {
@@ -86,10 +106,11 @@ export function createPatternContext(options) {
86
106
  });
87
107
  Object.freeze(node);
88
108
  const where = build({ node });
89
- if (nodes.length === 0) {
109
+ const [first, ...rest] = nodes;
110
+ if (first === undefined) {
90
111
  fail('BUILDER_POLICY_EMPTY_EXISTS', `${options.pointer}/match/exists/nodes`, 'exists(...) must declare at least one variable Node.');
91
112
  }
92
- return { exists: { nodes, where } };
113
+ return { exists: { nodes: [first, ...rest], where } };
93
114
  },
94
115
  allOf(...expressions) {
95
116
  nonEmpty(expressions, `${options.pointer}/match/allOf`);
@@ -102,6 +123,15 @@ export function createPatternContext(options) {
102
123
  };
103
124
  return Object.freeze(context);
104
125
  }
126
+ /** One Edge pattern, whichever form named its ends. */
127
+ function edgePattern(source, classRef, target, repeat) {
128
+ return {
129
+ source,
130
+ class: classRef,
131
+ target,
132
+ ...(repeat === undefined ? {} : { repeat: { ...repeat } }),
133
+ };
134
+ }
105
135
  function composition(operator, policies) {
106
136
  nonEmpty(policies, `/${operator}`);
107
137
  const value = {