@astrale-os/kernel-dsl 1.0.0-beta.16 → 1.0.0-beta.18

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 (75) hide show
  1. package/dist/v1/builder/class/authoring.d.ts +40 -4
  2. package/dist/v1/builder/class/authoring.js +3 -2
  3. package/dist/v1/builder/class/input.d.ts +45 -14
  4. package/dist/v1/builder/index.d.ts +2 -1
  5. package/dist/v1/builder/lowering/class.d.ts +8 -0
  6. package/dist/v1/builder/lowering/class.js +40 -16
  7. package/dist/v1/builder/lowering/schema/compile.js +3 -3
  8. package/dist/v1/builder/policy/authoring.js +97 -88
  9. package/dist/v1/bundle/model.js +1 -1
  10. package/dist/v1/compiled/admission/accept.js +1 -7
  11. package/dist/v1/compiled/admission/semantics/class.js +2 -3
  12. package/dist/v1/compiled/admission/semantics/policy/parity.js +25 -13
  13. package/dist/v1/compiled/admission/wire/class.js +23 -18
  14. package/dist/v1/compiled/admission/wire/policy.d.ts +4 -3
  15. package/dist/v1/compiled/admission/wire/policy.js +8 -7
  16. package/dist/v1/compiled/loader/load.js +1 -1
  17. package/dist/v1/domain/create.js +1 -1
  18. package/dist/v1/domain/model/input.d.ts +2 -1
  19. package/dist/v1/domain/projection/classes.js +1 -1
  20. package/dist/v1/domain/validation/create.d.ts +2 -1
  21. package/dist/v1/domain/validation/create.js +1 -1
  22. package/dist/v1/index.d.ts +1 -1
  23. package/dist/v1/language/callable/resolved.d.ts +11 -2
  24. package/dist/v1/language/class/model/class.d.ts +17 -5
  25. package/dist/v1/language/freeze.d.ts +5 -0
  26. package/dist/v1/{schema/canonical → language}/freeze.js +4 -0
  27. package/dist/v1/language/policy/analysis/typing.d.ts +1 -0
  28. package/dist/v1/language/policy/analysis/typing.js +74 -79
  29. package/dist/v1/language/policy/model/policy.d.ts +44 -4
  30. package/dist/v1/language/policy/model/policy.js +14 -0
  31. package/dist/v1/language/policy/normalization/normalize.d.ts +6 -6
  32. package/dist/v1/language/policy/normalization/normalize.js +5 -5
  33. package/dist/v1/language/validation/rule.d.ts +18 -8
  34. package/dist/v1/language/validation/rule.js +2 -11
  35. package/dist/v1/language/value/evaluate/program.d.ts +2 -1
  36. package/dist/v1/language/value/evaluate/program.js +2 -2
  37. package/dist/v1/language/value/evaluate/validate.d.ts +2 -2
  38. package/dist/v1/language/value/evaluate/validate.js +2 -2
  39. package/dist/v1/language/value/profile/json.validate.d.ts +3 -1
  40. package/dist/v1/language/value/profile/json.validate.js +3 -2
  41. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +180 -25
  42. package/dist/v1/schema/admission/callable.js +1 -1
  43. package/dist/v1/schema/canonical/revision.js +4 -4
  44. package/dist/v1/schema/compatibility/comparators/callables.js +1 -1
  45. package/dist/v1/schema/compatibility/comparators/classes.js +4 -4
  46. package/dist/v1/schema/compatibility/comparators/members.js +1 -1
  47. package/dist/v1/schema/compatibility/comparators/values.js +2 -2
  48. package/dist/v1/schema/compatibility/report.js +1 -1
  49. package/dist/v1/schema/compatibility/versioning/index.js +1 -1
  50. package/dist/v1/schema/model/schema.js +1 -1
  51. package/dist/v1/schema/normalization/domain.js +9 -8
  52. package/dist/v1/schema/resolution/domain.js +1 -1
  53. package/dist/v1/schema/resolution/origins.js +3 -2
  54. package/dist/v1/schema/rule.d.ts +8 -20
  55. package/dist/v1/schema/rule.js +1 -1
  56. package/dist/v1/schema/validation/class.js +2 -3
  57. package/dist/v1/schema/validation/domain.json-schema.d.ts +2 -2
  58. package/dist/v1/schema/validation/domain.json-schema.js +20 -6
  59. package/dist/v1/schema/validation/domain.structure.d.ts +4765 -1005
  60. package/dist/v1/schema/validation/domain.structure.js +21 -9
  61. package/dist/v1/schema/validation/json.json-schema.d.ts +18 -2
  62. package/dist/v1/schema/validation/json.json-schema.js +14 -4
  63. package/dist/v1/schema/validation/policy/budgets.d.ts +1 -1
  64. package/dist/v1/schema/validation/policy/budgets.js +4 -4
  65. package/dist/v1/schema/validation/policy/uses.d.ts +10 -2
  66. package/dist/v1/schema/validation/policy/uses.js +26 -13
  67. package/dist/v1/schema/validation/policy/validate.js +2 -3
  68. package/dist/v1/schema/validation/value.json-schema.d.ts +4 -6
  69. package/dist/v1/schema/validation/value.json-schema.js +18 -25
  70. package/package.json +1 -1
  71. package/dist/v1/schema/canonical/domain.d.ts +0 -3
  72. package/dist/v1/schema/canonical/domain.digest.d.ts +0 -3
  73. package/dist/v1/schema/canonical/domain.digest.js +0 -5
  74. package/dist/v1/schema/canonical/domain.js +0 -5
  75. package/dist/v1/schema/canonical/freeze.d.ts +0 -1
@@ -1,9 +1,9 @@
1
- import type { Value as PortableValue } from '@astrale-os/kernel-dsl/value';
1
+ import type { Array as PortableArray, Object as PortableObject, Value as PortableValue } from '@astrale-os/kernel-dsl/value';
2
2
  import type { z } from 'zod';
3
3
  import type { ClassRef } from '../../addressing/index.js';
4
4
  import type { ResolvedClass, ResolvedProperty } from '../../language/class/resolved.js';
5
5
  import type { StateTopologyOf } from '../../language/state/topology.js';
6
- import type { BlobReceipt, BlobRef, BlobSchema } from '../../language/value/model/blob.js';
6
+ import type { BLOB_VALUE_KIND, BlobReceipt, BlobRef, BlobSchema } from '../../language/value/model/blob.js';
7
7
  import type { Method, ProjectionOfCallable } from '../callable/authoring.js';
8
8
  import type { ClassProjectionOfResolved } from '../schema/projection.js';
9
9
  import type { TypedValueSchema } from '../value/schema.js';
@@ -14,8 +14,44 @@ import type { AutoProperty, Property, PropertyOptions, PropertyStateMachineOf, V
14
14
  type ExactEndpointInput<Input, Shape> = Input & Record<Exclude<keyof Input, keyof Shape>, never>;
15
15
  type PropertySchemaOf<Field> = Field extends AutoProperty<infer Value> ? PropertySchemaOf<Value> : Field extends Property<infer Schema, PropertyOptions, unknown> ? Schema : Field;
16
16
  type PropertyOptionsOf<Field> = Field extends AutoProperty<infer Value> ? PropertyOptionsOf<Value> : Field extends Property<ValueSchemaInput, infer Options, unknown> ? Options : Record<never, never>;
17
- type ValueInputOf<Schema> = Schema extends BlobSchema ? BlobReceipt : Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.input<Schema> : PortableValue;
18
- type ValueOutputOf<Schema> = Schema extends BlobSchema ? BlobRef : Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.output<Schema> : PortableValue;
17
+ type JsonSchemaValueOf<Schema> = 0 extends 1 & Schema ? PortableValue : Schema extends {
18
+ readonly const: infer Value;
19
+ } ? JsonKnownOf<Value> : Schema extends {
20
+ readonly enum: readonly (infer Value)[];
21
+ } ? JsonKnownOf<Value> : Schema extends {
22
+ readonly anyOf: readonly (infer Branch)[];
23
+ } ? JsonSchemaValueOf<Branch> : Schema extends {
24
+ readonly oneOf: readonly (infer Branch)[];
25
+ } ? JsonSchemaValueOf<Branch> : Schema extends {
26
+ readonly type: infer Type;
27
+ } ? JsonTypeValueOf<Type, Schema> : PortableValue;
28
+ type JsonKnownOf<Value> = 0 extends 1 & Value ? PortableValue : Value;
29
+ type JsonTypeValueOf<Type, Schema> = Type extends readonly (infer Each)[] ? JsonTypeValueOf<Each, Schema> : Type extends 'string' ? string : Type extends 'number' | 'integer' ? number : Type extends 'boolean' ? boolean : Type extends 'null' ? null : Type extends 'array' ? JsonArrayValueOf<Schema> : Type extends 'object' ? JsonObjectValueOf<Schema> : PortableValue;
30
+ type JsonArrayValueOf<Schema> = 'prefixItems' extends keyof Schema ? PortableArray : Schema extends {
31
+ readonly items: infer Items;
32
+ } ? readonly JsonSchemaValueOf<Items>[] : PortableArray;
33
+ type JsonObjectValueOf<Schema> = 'patternProperties' extends keyof Schema ? PortableObject : Schema extends {
34
+ readonly properties: infer Properties extends Readonly<Record<string, unknown>>;
35
+ readonly additionalProperties: false;
36
+ } ? JsonClosedObjectOf<Properties, Schema extends {
37
+ readonly required: infer Names;
38
+ } ? Names : readonly []> : PortableObject;
39
+ type JsonListsName<Names, Name> = [Names] extends [never] ? false : Names extends readonly unknown[] ? number extends Names['length'] ? false : true extends {
40
+ [At in keyof Names]: JsonNamesExactly<Names[At], Name>;
41
+ }[number] ? true : false : false;
42
+ type JsonNamesExactly<Element, Name> = [Element] extends [never] ? false : 0 extends 1 & Element ? false : [Element] extends [Name] ? true : false;
43
+ type JsonClosedObjectOf<Properties, Names> = [keyof Properties] extends [never] ? Readonly<Record<string, never>> : {
44
+ readonly [Name in keyof Properties as JsonListsName<Names, Name> extends true ? Name : never]: JsonSchemaValueOf<Properties[Name]>;
45
+ } & {
46
+ readonly [Name in keyof Properties as JsonListsName<Names, Name> extends true ? never : Name]?: JsonSchemaValueOf<Properties[Name]>;
47
+ } extends infer Value ? {
48
+ [Name in keyof Value]: Value[Name];
49
+ } : never;
50
+ type BlobMarkedSchema = BlobSchema | {
51
+ readonly 'x-astrale-value-kind': typeof BLOB_VALUE_KIND;
52
+ };
53
+ type ValueInputOf<Schema> = Schema extends BlobMarkedSchema ? BlobReceipt : Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.input<Schema> : JsonSchemaValueOf<Schema>;
54
+ type ValueOutputOf<Schema> = Schema extends BlobMarkedSchema ? BlobRef : Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.output<Schema> : JsonSchemaValueOf<Schema>;
19
55
  type PropertyRequiredOf<Field> = PropertyOptionsOf<Field> extends {
20
56
  readonly required: infer Required extends boolean;
21
57
  } ? Required : undefined extends ValueInputOf<PropertySchemaOf<Field>> ? false : true;
@@ -1,3 +1,4 @@
1
+ import { CLASS_OPERATIONS } from '../../language/policy/model/policy.js';
1
2
  import { assertKnownKeys } from '../schema/diagnostic.js';
2
3
  import { authoringValue } from '../schema/handle.js';
3
4
  const classInputs = new WeakMap();
@@ -28,7 +29,7 @@ export function nodeClass(config) {
28
29
  'unique',
29
30
  ]);
30
31
  if (admitted.policies !== undefined)
31
- assertKnownKeys(admitted.policies, ['read', 'traverse']);
32
+ assertKnownKeys(admitted.policies, CLASS_OPERATIONS);
32
33
  const value = authoringValue('definition', {
33
34
  kind: 'node',
34
35
  });
@@ -125,7 +126,7 @@ function validateIncidentEndpointInput(value) {
125
126
  /** Check the nested Edge Class options; the caller has checked the option keys themselves. */
126
127
  function validateEdgeClassOptions(value, constraints) {
127
128
  if (value.policies !== undefined)
128
- assertKnownKeys(value.policies, ['read', 'traverse']);
129
+ assertKnownKeys(value.policies, CLASS_OPERATIONS);
129
130
  if (value.constraints !== undefined)
130
131
  assertKnownKeys(value.constraints, constraints);
131
132
  }
@@ -3,7 +3,7 @@ import type { Inheritance } from '../../language/class/model/class.js';
3
3
  import type { MediaType } from '../../language/class/model/data.js';
4
4
  import type { EdgeCount, EdgeLifetime } from '../../language/class/model/edge.js';
5
5
  import type { ResolvedClass } from '../../language/class/resolved.js';
6
- import type { ClassRuleChecks, ClassRuleObject } from '../../language/policy/model/policy.js';
6
+ import type { ClassRuleChecks, ClassRuleObject, CreateRuleChecks, CreateRuleObject, EdgeRuleChecks } from '../../language/policy/model/policy.js';
7
7
  import type { Method, PolicyCheckContext } from '../callable/authoring.js';
8
8
  import type { Policy } from '../policy/authoring.js';
9
9
  import type { Lazy } from '../schema/lazy.js';
@@ -37,26 +37,57 @@ export interface IncidentEndpointInput {
37
37
  /** What deleting a Node at this endpoint does to the Edge; derived when omitted. */
38
38
  readonly onDelete?: EdgeLifetime;
39
39
  }
40
- /** One observation slot: a local Policy, or its constant value. Absent means `true`. */
40
+ /**
41
+ * One Class slot: a local Policy, or its constant value. An omitted observation admits every
42
+ * caller, as `true` does; an omitted write admits only the owning Domain, as `false` does.
43
+ */
41
44
  export type ClassPolicyInput = Lazy<Policy> | boolean;
42
- /** Local observation Policies of a Class. */
43
- export interface ClassPoliciesInput {
44
- readonly read?: ClassPolicyInput;
45
- readonly traverse?: ClassPolicyInput;
46
- }
47
45
  /**
48
- * Policy checks deciding one Node Class observation. `self` is the observed Node; `ref` names one
49
- * exact member, the same Node for every observed candidate.
46
+ * Policy checks deciding one Node Class slot. `self` is the observed or written Node; `ref` names
47
+ * one exact member, the same Node for every candidate.
50
48
  */
51
49
  export interface ClassRuleContext extends PolicyCheckContext<ClassRuleObject> {
52
50
  }
53
51
  export type ClassRuleBuilder = (context: ClassRuleContext) => ClassRuleChecks;
54
- /** One Node Class observation slot: a local Policy, a constant value, or Policy checks. */
52
+ /** One Node Class slot: a local Policy, a constant value, or Policy checks. */
55
53
  export type NodeClassPolicyInput = ClassPolicyInput | ClassRuleBuilder;
56
- /** Local observation Policies of a Node Class. */
54
+ /**
55
+ * Policy checks deciding the creation of a Node. The Node is not yet, so it has no `self`: `ref`
56
+ * names each object, one exact member.
57
+ */
58
+ export interface CreateRuleContext extends Omit<PolicyCheckContext<CreateRuleObject>, 'self'> {
59
+ }
60
+ export type CreateRuleBuilder = (context: CreateRuleContext) => CreateRuleChecks;
61
+ /**
62
+ * The Node Class create slot: a constant value, or Policy checks over members. A Policy alone
63
+ * would have no object there.
64
+ */
65
+ export type NodeClassCreatePolicyInput = boolean | CreateRuleBuilder;
66
+ /** Local Policies of a Node Class. */
57
67
  export interface NodeClassPoliciesInput {
58
68
  readonly read?: NodeClassPolicyInput;
59
69
  readonly traverse?: NodeClassPolicyInput;
70
+ readonly create?: NodeClassCreatePolicyInput;
71
+ readonly update?: NodeClassPolicyInput;
72
+ readonly delete?: NodeClassPolicyInput;
73
+ }
74
+ /**
75
+ * Policy checks deciding one Edge Class slot. `self` is the observed or written Edge, checked by a
76
+ * Policy whose `source` and `target` are its endpoints; `ref` names one exact member, the same Node
77
+ * for every candidate.
78
+ */
79
+ export interface EdgeRuleContext extends PolicyCheckContext<ClassRuleObject> {
80
+ }
81
+ export type EdgeRuleBuilder = (context: EdgeRuleContext) => EdgeRuleChecks;
82
+ /** One Edge Class slot: a local Policy, a constant value, or Policy checks. */
83
+ export type EdgeClassPolicyInput = ClassPolicyInput | EdgeRuleBuilder;
84
+ /** Local Policies of an Edge Class. */
85
+ export interface EdgeClassPoliciesInput {
86
+ readonly read?: EdgeClassPolicyInput;
87
+ readonly traverse?: EdgeClassPolicyInput;
88
+ readonly create?: EdgeClassPolicyInput;
89
+ readonly update?: EdgeClassPolicyInput;
90
+ readonly delete?: EdgeClassPolicyInput;
60
91
  }
61
92
  /** Authored Node Class. */
62
93
  export interface NodeClassInput {
@@ -69,7 +100,7 @@ export interface NodeClassInput {
69
100
  readonly data?: MediaType;
70
101
  readonly methods?: Readonly<Record<string, Method>>;
71
102
  readonly icon?: string;
72
- /** Local observation Policies; inherited by no descendant and forbidden on abstract Classes. */
103
+ /** Local Policies; inherited by no descendant and forbidden on abstract Classes. */
73
104
  readonly policies?: NodeClassPoliciesInput;
74
105
  /**
75
106
  * The tuples no two Nodes of this Class and of its active concrete descendants share while every
@@ -90,8 +121,8 @@ export interface EdgeClassOptions {
90
121
  readonly description?: string;
91
122
  readonly extends?: readonly EdgeClassReferenceInput[];
92
123
  readonly properties?: PropertyRecordInput;
93
- /** Local observation Policies; inherited by no descendant and forbidden on abstract Classes. */
94
- readonly policies?: ClassPoliciesInput;
124
+ /** Local Policies; inherited by no descendant and forbidden on abstract Classes. */
125
+ readonly policies?: EdgeClassPoliciesInput;
95
126
  readonly constraints?: {
96
127
  readonly noSelf?: true;
97
128
  readonly acyclic?: true;
@@ -15,7 +15,8 @@ export type { BuilderDiagnostic as Diagnostic, BuilderDiagnosticCode as Diagnost
15
15
  export type { CallableInputBase, CallableInput, BinaryInputAuthoring, CallableOutputInput, CallableProjection, CallablePolicyBuilder, CallablePolicyContext, PolicyCheckContext, BinaryOutput, Function, FunctionInput, Method, MethodInput, PolicyObjectInput, StreamOutput, } from './callable/authoring.js';
16
16
  export type { CoreEdge, CoreEndpoint, CoreInput, CoreNode } from './core/authoring.js';
17
17
  export type { Class, DirectedEdgeClass, EdgeClass, NodeClass, UndirectedEdgeClass, } from './class/handle.js';
18
- export type { ClassReferenceInput as ClassReference, ClassRuleBuilder, ClassRuleContext, DirectedEdgeInput, DirectedSourceInput, DirectedTargetInput, EdgeClassCommon, EdgeClassInput, EdgeClassReferenceInput as EdgeClassReference, EdgeUniqueFieldInput, EdgeUniqueTupleInput, IncidentEndpointInput, NodeClassInput, NodeClassPoliciesInput, NodeClassPolicyInput, NodeClassReferenceInput as NodeClassReference, NodeUniqueTupleInput, UndirectedEdgeInput, } from './class/input.js';
18
+ export type { ClassReferenceInput as ClassReference, ClassRuleBuilder, ClassRuleContext, CreateRuleBuilder, CreateRuleContext, DirectedEdgeInput, DirectedSourceInput, DirectedTargetInput, EdgeClassCommon, EdgeClassInput, EdgeClassPoliciesInput, EdgeClassPolicyInput, EdgeClassReferenceInput as EdgeClassReference, EdgeRuleBuilder, EdgeRuleContext, EdgeUniqueFieldInput, EdgeUniqueTupleInput, IncidentEndpointInput, NodeClassCreatePolicyInput, NodeClassInput, NodeClassPoliciesInput, NodeClassPolicyInput, NodeClassReferenceInput as NodeClassReference, NodeUniqueTupleInput, UndirectedEdgeInput, } from './class/input.js';
19
+ export type { ClassRuleChecks, CreateRuleChecks, EdgeRuleChecks, } from '../language/policy/model/policy.js';
19
20
  export type { AuthoredPropertyProjection, AuthoredMethodProjection, ClassIdentityProjection, ClassOwnerProjection, ClassReferenceProjection, } from './class/projection.js';
20
21
  export type { PathAuthoring } from './value/path.js';
21
22
  export type { Lazy } from './schema/lazy.js';
@@ -13,13 +13,21 @@ export declare class V1DefinitionCompiler {
13
13
  compileClass(value: Class, name: string, pointer: string): ClassDefinition;
14
14
  private nodeClass;
15
15
  private edgeClass;
16
+ /**
17
+ * An omitted observation, like `true`, lowers to the absent slot a Schema reads as every caller
18
+ * (A18 defers X1). A write slot has the converse spelling: `false` and an omitted slot lower to
19
+ * the absent slot, owner only.
20
+ */
16
21
  private classPolicies;
17
22
  /**
18
23
  * A lazy Policy ignores the context it is called with; a rule builder returns Policy checks. Both
19
24
  * are functions, so the result tells them apart.
20
25
  */
21
26
  private classPolicy;
27
+ /** A Node create rule checks members only: the Node it creates is not yet. */
28
+ private createRule;
22
29
  private classRuleContext;
30
+ private createRuleContext;
23
31
  private endpointBase;
24
32
  private directedSource;
25
33
  private directedTarget;
@@ -1,6 +1,6 @@
1
1
  import { compareDefinitionRefs } from '../../addressing/ordering.js';
2
2
  import { isMethodExecutable } from '../../language/callable/executable.js';
3
- import { isClassRuleChecks } from '../../language/policy/model/policy.js';
3
+ import { CLASS_OPERATIONS, isClassObservation, isClassRuleChecks, } from '../../language/policy/model/policy.js';
4
4
  import { normalizeClassRule } from '../../language/policy/normalization/normalize.js';
5
5
  import { callableConfiguration } from '../callable/authoring.js';
6
6
  import { classConfiguration } from '../class/authoring.js';
@@ -88,43 +88,57 @@ export class V1DefinitionCompiler {
88
88
  extends: this.parents(input.extends, 'edge', `${pointer}/extends`),
89
89
  properties: facet,
90
90
  ...contract,
91
- // classPolicy refuses Policy checks on an Edge Class, so only Class Policies remain.
92
91
  policies: this.classPolicies(input, 'edge', `${pointer}/policies`),
93
92
  };
94
93
  }
94
+ /**
95
+ * An omitted observation, like `true`, lowers to the absent slot a Schema reads as every caller
96
+ * (A18 defers X1). A write slot has the converse spelling: `false` and an omitted slot lower to
97
+ * the absent slot, owner only.
98
+ */
95
99
  classPolicies(input, kind, pointer) {
96
100
  const slot = (operation) => {
97
101
  const value = input.policies?.[operation];
102
+ const observation = isClassObservation(operation);
98
103
  if (value === undefined)
99
104
  return undefined;
100
105
  if (typeof value !== 'boolean')
101
- return this.classPolicy(value, kind, `${pointer}/${operation}`);
102
- // `true` on an abstract Class would otherwise lower to nothing and pass unnoticed.
106
+ return this.classPolicy(value, kind, operation, `${pointer}/${operation}`);
107
+ // A constant on an abstract Class would otherwise lower to nothing and pass unnoticed.
103
108
  if (input.abstract === true) {
104
- fail('BUILDER_ABSTRACT_POLICY', `${pointer}/${operation}`, 'Abstract Classes cannot declare observation Policies.');
109
+ fail('BUILDER_ABSTRACT_POLICY', `${pointer}/${operation}`, 'Abstract Classes cannot declare Class Policies.');
105
110
  }
106
- // `true` is the meaning of an absent observation Policy, so it lowers to none.
107
- return value ? undefined : false;
108
- };
109
- const read = slot('read');
110
- const traverse = slot('traverse');
111
- return {
112
- ...(read === undefined ? {} : { read }),
113
- ...(traverse === undefined ? {} : { traverse }),
111
+ // Each slot spells only the constant its absence does not mean.
112
+ if (observation)
113
+ return value ? undefined : false;
114
+ return value ? true : undefined;
114
115
  };
116
+ return Object.fromEntries(CLASS_OPERATIONS.flatMap((operation) => {
117
+ const policy = slot(operation);
118
+ return policy === undefined ? [] : [[operation, policy]];
119
+ }));
115
120
  }
116
121
  /**
117
122
  * A lazy Policy ignores the context it is called with; a rule builder returns Policy checks. Both
118
123
  * are functions, so the result tells them apart.
119
124
  */
120
- classPolicy(value, kind, pointer) {
125
+ classPolicy(value, kind, operation, pointer) {
126
+ if (kind === 'node' && operation === 'create')
127
+ return this.createRule(value, pointer);
121
128
  if (typeof value !== 'function')
122
129
  return this.policies.ref(value, pointer);
123
130
  const result = value(this.classRuleContext(pointer));
124
131
  if (!isClassRuleResult(result))
125
132
  return this.policies.ref(result, pointer);
126
- if (kind === 'edge') {
127
- fail('BUILDER_POLICY', pointer, 'An Edge Class observation names one Policy, true or false.');
133
+ return normalizeClassRule(result);
134
+ }
135
+ /** A Node create rule checks members only: the Node it creates is not yet. */
136
+ createRule(value, pointer) {
137
+ const result = typeof value === 'function'
138
+ ? value(this.createRuleContext(pointer))
139
+ : undefined;
140
+ if (!isClassRuleResult(result)) {
141
+ fail('BUILDER_POLICY', pointer, 'A Node create rule checks Policies of ref(...) members: a Policy alone has no object there.');
128
142
  }
129
143
  return normalizeClassRule(result);
130
144
  }
@@ -135,6 +149,16 @@ export class V1DefinitionCompiler {
135
149
  fail('BUILDER_POLICY_INPUT', pointer, 'A Class rule checks self or one ref(...) member.');
136
150
  });
137
151
  }
152
+ createRuleContext(pointer) {
153
+ return policyCheckContext(this.context, pointer, (object) => {
154
+ if (object?.kind === 'ref')
155
+ return object;
156
+ if (object?.kind === 'self') {
157
+ fail('BUILDER_POLICY', pointer, 'A Node create rule has no self: the Node is not yet.');
158
+ }
159
+ fail('BUILDER_POLICY_INPUT', pointer, 'A Node create rule checks one ref(...) member.');
160
+ });
161
+ }
138
162
  endpointBase(value, pointer) {
139
163
  return {
140
164
  role: value.as,
@@ -1,7 +1,7 @@
1
+ import { deepFreeze } from '../../../language/freeze.js';
1
2
  import { acceptDomainSchema } from '../../../schema/admission/accept.js';
2
- import { canonicalizeDomainSchema } from '../../../schema/canonical/domain.js';
3
- import { deepFreeze } from '../../../schema/canonical/freeze.js';
4
3
  import { MemoryDomainSchemaLookup } from '../../../schema/dependency/lookup.js';
4
+ import { normalizeDomainSchema } from '../../../schema/normalization/domain.js';
5
5
  import { lookupEntriesForDependencies } from '../../../schema/resolution/closure.js';
6
6
  import { parseDomainSchemaStructure } from '../../../schema/validation/domain.structure.js';
7
7
  import { assertKnownKeys, escapeToken } from '../../schema/diagnostic.js';
@@ -90,7 +90,7 @@ function assembleDomainSchema(origin, input, lookup) {
90
90
  dependencies: context.dependencies(),
91
91
  };
92
92
  const structured = parseDomainSchemaStructure(candidate);
93
- const canonical = parseDomainSchemaStructure(canonicalizeDomainSchema(structured));
93
+ const canonical = parseDomainSchemaStructure(normalizeDomainSchema(structured));
94
94
  return deepFreeze(canonical);
95
95
  }
96
96
  function mapRecord(record, map) {
@@ -21,97 +21,24 @@ function createPolicy(input) {
21
21
  declarations.set(value, input);
22
22
  return value;
23
23
  }
24
- export const policy = Object.freeze(Object.assign(createPolicy, {
25
- allOf: (first, ...rest) => createPolicy(composition('allOf', first === undefined ? [] : [first, ...rest])),
26
- anyOf: (first, ...rest) => createPolicy(composition('anyOf', first === undefined ? [] : [first, ...rest])),
27
- }));
24
+ export const policy = Object.freeze(Object.assign(createPolicy, { allOf: composer('allOf'), anyOf: composer('anyOf') }));
25
+ function composer(operator) {
26
+ return (first, ...rest) => createPolicy(composition(operator, first === undefined ? [] : [first, ...rest]));
27
+ }
28
28
  export function createPatternContext(options) {
29
- let nextVariable = 0;
30
- const subject = Object.freeze({ kind: 'subject' });
31
- const object = Object.freeze({ kind: 'object' });
32
- const source = Object.freeze({ kind: 'source' });
33
- const target = Object.freeze({ kind: 'target' });
34
- const classRef = (value) => options.resolveEdgeClass(value, `${options.pointer}/match/class`);
29
+ const nextVariable = variableIds();
35
30
  const context = {
36
31
  ref: (member) => ({
37
32
  kind: 'ref',
38
33
  ref: options.resolveObject(member, `${options.pointer}/match/ref`),
39
34
  }),
40
35
  sameNode: (left, right) => ({ sameNode: { left, right } }),
41
- subject,
42
- object,
43
- source,
44
- target,
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
- }),
75
- exists(build) {
76
- const nodes = [];
77
- const bind = (selector, definition) => {
78
- const variable = Object.freeze({ kind: 'variable', id: nextVariable++ });
79
- if (selector === 'any') {
80
- nodes.push({ variable, selector: Object.freeze({ kind: 'any' }) });
81
- }
82
- else {
83
- const classRef = options.resolveNodeClass(definition, `${options.pointer}/match/exists/nodes`);
84
- nodes.push(selector === 'exact'
85
- ? { variable, class: classRef }
86
- : { variable, selector: Object.freeze({ kind: 'satisfies', class: classRef }) });
87
- }
88
- return variable;
89
- };
90
- const node = function (...args) {
91
- if (args.length > 1 || (args.length === 1 && args[0] === undefined)) {
92
- fail('BUILDER_POLICY', `${options.pointer}/match/exists/nodes`, 'node(...) accepts either no arguments or one Node Class.');
93
- }
94
- return args.length === 0
95
- ? bind('any')
96
- : bind('satisfies', args[0]);
97
- };
98
- Object.defineProperty(node, 'exact', {
99
- enumerable: true,
100
- value(definition) {
101
- if (arguments.length !== 1 || definition === undefined) {
102
- fail('BUILDER_POLICY', `${options.pointer}/match/exists/nodes`, 'node.exact(...) requires exactly one Node Class.');
103
- }
104
- return bind('exact', definition);
105
- },
106
- });
107
- Object.freeze(node);
108
- const where = build({ node });
109
- const [first, ...rest] = nodes;
110
- if (first === undefined) {
111
- fail('BUILDER_POLICY_EMPTY_EXISTS', `${options.pointer}/match/exists/nodes`, 'exists(...) must declare at least one variable Node.');
112
- }
113
- return { exists: { nodes: [first, ...rest], where } };
114
- },
36
+ subject: Object.freeze({ kind: 'subject' }),
37
+ object: Object.freeze({ kind: 'object' }),
38
+ source: Object.freeze({ kind: 'source' }),
39
+ target: Object.freeze({ kind: 'target' }),
40
+ edge: (...args) => edge(options, args),
41
+ exists: (build) => exists(options, nextVariable, build),
115
42
  allOf(...expressions) {
116
43
  nonEmpty(expressions, `${options.pointer}/match/allOf`);
117
44
  return { allOf: expressions };
@@ -123,6 +50,50 @@ export function createPatternContext(options) {
123
50
  };
124
51
  return Object.freeze(context);
125
52
  }
53
+ /** Variable ids, unique across every `exists(...)` of one pattern. */
54
+ function variableIds() {
55
+ let next = 0;
56
+ return () => next++;
57
+ }
58
+ function edge(options, args) {
59
+ const pointer = `${options.pointer}/match/edge`;
60
+ if (args[1] === undefined)
61
+ return legacyEdge(options, args[0], pointer);
62
+ const [classValue, ends, edgeOptions] = args;
63
+ const ref = options.resolveEdgeClass(classValue, `${options.pointer}/match/class`);
64
+ const [sourceRole, targetRole] = options.edgeRoles(ref);
65
+ if (typeof ends !== 'object' || ends === null) {
66
+ fail('BUILDER_POLICY', pointer, 'edge(classValue, ends) names both ends by their roles.');
67
+ }
68
+ assertKnownKeys(ends, [sourceRole, targetRole], pointer);
69
+ const missing = `edge(...) names both ends of ${ref.origin}:class.${ref.name}: "${sourceRole}" and "${targetRole}".`;
70
+ const sourceTerm = endTerm(ends, sourceRole, pointer, missing);
71
+ const targetTerm = endTerm(ends, targetRole, pointer, missing);
72
+ if (edgeOptions !== undefined)
73
+ assertKnownKeys(edgeOptions, ['repeat'], pointer);
74
+ if (edgeOptions?.repeat !== undefined) {
75
+ assertKnownKeys(edgeOptions.repeat, ['min', 'max'], `${pointer}/repeat`);
76
+ }
77
+ return edgePattern(sourceTerm, ref, targetTerm, edgeOptions?.repeat);
78
+ }
79
+ function legacyEdge(options, input, pointer) {
80
+ // Typed callers cannot pass an Edge Class without ends; untyped ones can, and `kind` is read
81
+ // without `in` so a primitive still reaches `assertKnownKeys`.
82
+ if (typeof input === 'function' || input.kind === 'edge') {
83
+ fail('BUILDER_POLICY', pointer, 'edge(classValue, ends) names both ends by their roles.');
84
+ }
85
+ assertKnownKeys(input, ['source', 'class', 'target', 'repeat']);
86
+ if (input.repeat !== undefined)
87
+ assertKnownKeys(input.repeat, ['min', 'max']);
88
+ const ref = options.resolveEdgeClass(input.class, `${options.pointer}/match/class`);
89
+ return edgePattern(input.source, ref, input.target, input.repeat);
90
+ }
91
+ /** The term of one role. An own key holding `undefined` passes through, as it always has. */
92
+ function endTerm(ends, role, pointer, missing) {
93
+ if (!Object.hasOwn(ends, role))
94
+ fail('BUILDER_POLICY', `${pointer}/${escapeToken(role)}`, missing);
95
+ return ends[role];
96
+ }
126
97
  /** One Edge pattern, whichever form named its ends. */
127
98
  function edgePattern(source, classRef, target, repeat) {
128
99
  return {
@@ -132,12 +103,50 @@ function edgePattern(source, classRef, target, repeat) {
132
103
  ...(repeat === undefined ? {} : { repeat: { ...repeat } }),
133
104
  };
134
105
  }
106
+ function exists(options, nextVariable, build) {
107
+ const pointer = `${options.pointer}/match/exists/nodes`;
108
+ const nodes = [];
109
+ const bind = (selector) => {
110
+ const variable = Object.freeze({ kind: 'variable', id: nextVariable() });
111
+ nodes.push(nodeVariable(options, variable, selector, pointer));
112
+ return variable;
113
+ };
114
+ const where = build({ node: nodeBinder(bind, pointer) });
115
+ const [first, ...rest] = nodes;
116
+ if (first === undefined) {
117
+ fail('BUILDER_POLICY_EMPTY_EXISTS', pointer, 'exists(...) must declare at least one variable Node.');
118
+ }
119
+ return { exists: { nodes: [first, ...rest], where } };
120
+ }
121
+ function nodeVariable(options, variable, selector, pointer) {
122
+ if (selector.kind === 'any')
123
+ return { variable, selector: Object.freeze({ kind: 'any' }) };
124
+ const classRef = options.resolveNodeClass(selector.definition, pointer);
125
+ if (selector.kind === 'exact')
126
+ return { variable, class: classRef };
127
+ return { variable, selector: Object.freeze({ kind: 'satisfies', class: classRef }) };
128
+ }
129
+ function nodeBinder(bind, pointer) {
130
+ function node(...args) {
131
+ const definition = args[0];
132
+ if (args.length > 1 || (args.length === 1 && definition === undefined)) {
133
+ fail('BUILDER_POLICY', pointer, 'node(...) accepts either no arguments or one Node Class.');
134
+ }
135
+ return definition === undefined
136
+ ? bind({ kind: 'any' })
137
+ : bind({ kind: 'satisfies', definition });
138
+ }
139
+ function exact(definition) {
140
+ if (arguments.length !== 1 || definition === undefined) {
141
+ fail('BUILDER_POLICY', pointer, 'node.exact(...) requires exactly one Node Class.');
142
+ }
143
+ return bind({ kind: 'exact', definition });
144
+ }
145
+ return Object.freeze(Object.assign(node, { exact }));
146
+ }
135
147
  function composition(operator, policies) {
136
148
  nonEmpty(policies, `/${operator}`);
137
- const value = {
138
- operator,
139
- policies: [...policies],
140
- };
149
+ const value = { operator, policies: [...policies] };
141
150
  compositions.add(value);
142
151
  return value;
143
152
  }
@@ -1,4 +1,4 @@
1
- import { deepFreeze } from '../schema/canonical/freeze.js';
1
+ import { deepFreeze } from '../language/freeze.js';
2
2
  export const DOMAIN_BUNDLE_FORMAT = 'astrale.dsl.bundle';
3
3
  export const DOMAIN_BUNDLE_VERSION = 'v1';
4
4
  export function asDomainSchemaBundle(root, closure) {
@@ -1,4 +1,5 @@
1
1
  import { accept as acceptPortable } from '@astrale-os/kernel-dsl/value';
2
+ import { deepFreeze } from '../../language/freeze.js';
2
3
  import { limits } from '../../language/limits.js';
3
4
  import {} from '../model/schema.js';
4
5
  import { validateClosure } from './closure.js';
@@ -69,10 +70,3 @@ export function acceptCompiledSchema(input) {
69
70
  validateCompiledSemantics(candidate);
70
71
  return deepFreeze(candidate);
71
72
  }
72
- function deepFreeze(input) {
73
- if (input === null || typeof input !== 'object' || Object.isFrozen(input))
74
- return input;
75
- for (const value of Object.values(input))
76
- deepFreeze(value);
77
- return Object.freeze(input);
78
- }
@@ -90,9 +90,8 @@ function programKey(entry, value) {
90
90
  /** Check automatic Properties, immutability and abstract Class obligations. */
91
91
  export function validateClassFacets(context) {
92
92
  for (const located of context.classes.values()) {
93
- if (located.value.abstract &&
94
- (located.value.policies.read !== undefined || located.value.policies.traverse !== undefined)) {
95
- fail(`${located.pointer}/policies`, 'Abstract Classes cannot declare observation Policies.');
93
+ if (located.value.abstract && Object.keys(located.value.policies).length > 0) {
94
+ fail(`${located.pointer}/policies`, 'Abstract Classes cannot declare Class Policies.');
96
95
  }
97
96
  for (const [index, property] of located.value.properties.entries()) {
98
97
  const path = `${located.pointer}/properties/${index}`;