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

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 (74) hide show
  1. package/dist/v1/builder/class/authoring.js +3 -2
  2. package/dist/v1/builder/class/input.d.ts +45 -14
  3. package/dist/v1/builder/index.d.ts +2 -1
  4. package/dist/v1/builder/lowering/class.d.ts +8 -0
  5. package/dist/v1/builder/lowering/class.js +40 -16
  6. package/dist/v1/builder/lowering/schema/compile.js +3 -3
  7. package/dist/v1/builder/policy/authoring.js +97 -88
  8. package/dist/v1/bundle/model.js +1 -1
  9. package/dist/v1/compiled/admission/accept.js +1 -7
  10. package/dist/v1/compiled/admission/semantics/class.js +2 -3
  11. package/dist/v1/compiled/admission/semantics/policy/parity.js +25 -13
  12. package/dist/v1/compiled/admission/wire/class.js +23 -18
  13. package/dist/v1/compiled/admission/wire/policy.d.ts +4 -3
  14. package/dist/v1/compiled/admission/wire/policy.js +8 -7
  15. package/dist/v1/compiled/loader/load.js +1 -1
  16. package/dist/v1/domain/create.js +1 -1
  17. package/dist/v1/domain/model/input.d.ts +2 -1
  18. package/dist/v1/domain/projection/classes.js +1 -1
  19. package/dist/v1/domain/validation/create.d.ts +2 -1
  20. package/dist/v1/domain/validation/create.js +1 -1
  21. package/dist/v1/index.d.ts +1 -1
  22. package/dist/v1/language/callable/resolved.d.ts +11 -2
  23. package/dist/v1/language/class/model/class.d.ts +17 -5
  24. package/dist/v1/language/freeze.d.ts +5 -0
  25. package/dist/v1/{schema/canonical → language}/freeze.js +4 -0
  26. package/dist/v1/language/policy/analysis/typing.d.ts +1 -0
  27. package/dist/v1/language/policy/analysis/typing.js +74 -79
  28. package/dist/v1/language/policy/model/policy.d.ts +44 -4
  29. package/dist/v1/language/policy/model/policy.js +14 -0
  30. package/dist/v1/language/policy/normalization/normalize.d.ts +6 -6
  31. package/dist/v1/language/policy/normalization/normalize.js +5 -5
  32. package/dist/v1/language/validation/rule.d.ts +18 -8
  33. package/dist/v1/language/validation/rule.js +2 -11
  34. package/dist/v1/language/value/evaluate/program.d.ts +2 -1
  35. package/dist/v1/language/value/evaluate/program.js +2 -2
  36. package/dist/v1/language/value/evaluate/validate.d.ts +2 -2
  37. package/dist/v1/language/value/evaluate/validate.js +2 -2
  38. package/dist/v1/language/value/profile/json.validate.d.ts +3 -1
  39. package/dist/v1/language/value/profile/json.validate.js +3 -2
  40. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +180 -25
  41. package/dist/v1/schema/admission/callable.js +1 -1
  42. package/dist/v1/schema/canonical/revision.js +4 -4
  43. package/dist/v1/schema/compatibility/comparators/callables.js +1 -1
  44. package/dist/v1/schema/compatibility/comparators/classes.js +4 -4
  45. package/dist/v1/schema/compatibility/comparators/members.js +1 -1
  46. package/dist/v1/schema/compatibility/comparators/values.js +2 -2
  47. package/dist/v1/schema/compatibility/report.js +1 -1
  48. package/dist/v1/schema/compatibility/versioning/index.js +1 -1
  49. package/dist/v1/schema/model/schema.js +1 -1
  50. package/dist/v1/schema/normalization/domain.js +9 -8
  51. package/dist/v1/schema/resolution/domain.js +1 -1
  52. package/dist/v1/schema/resolution/origins.js +3 -2
  53. package/dist/v1/schema/rule.d.ts +8 -20
  54. package/dist/v1/schema/rule.js +1 -1
  55. package/dist/v1/schema/validation/class.js +2 -3
  56. package/dist/v1/schema/validation/domain.json-schema.d.ts +2 -2
  57. package/dist/v1/schema/validation/domain.json-schema.js +20 -6
  58. package/dist/v1/schema/validation/domain.structure.d.ts +4765 -1005
  59. package/dist/v1/schema/validation/domain.structure.js +21 -9
  60. package/dist/v1/schema/validation/json.json-schema.d.ts +18 -2
  61. package/dist/v1/schema/validation/json.json-schema.js +14 -4
  62. package/dist/v1/schema/validation/policy/budgets.d.ts +1 -1
  63. package/dist/v1/schema/validation/policy/budgets.js +4 -4
  64. package/dist/v1/schema/validation/policy/uses.d.ts +10 -2
  65. package/dist/v1/schema/validation/policy/uses.js +26 -13
  66. package/dist/v1/schema/validation/policy/validate.js +2 -3
  67. package/dist/v1/schema/validation/value.json-schema.d.ts +4 -6
  68. package/dist/v1/schema/validation/value.json-schema.js +18 -25
  69. package/package.json +1 -1
  70. package/dist/v1/schema/canonical/domain.d.ts +0 -3
  71. package/dist/v1/schema/canonical/domain.digest.d.ts +0 -3
  72. package/dist/v1/schema/canonical/domain.digest.js +0 -5
  73. package/dist/v1/schema/canonical/domain.js +0 -5
  74. package/dist/v1/schema/canonical/freeze.d.ts +0 -1
@@ -2,6 +2,7 @@ import {} from '@astrale-os/kernel-dsl/value';
2
2
  import { Key } from '../../../addressing/index.js';
3
3
  import { MEDIA_TYPE_PATTERN } from '../../../language/class/model/data.js';
4
4
  import { compareEdgeUniqueTuples, compareNodeUniqueTuples, edgeUniqueTuple, nodeUniqueTuple, } from '../../../language/class/unique.js';
5
+ import { CLASS_OPERATIONS, isClassObservation } from '../../../language/policy/model/policy.js';
5
6
  import {} from '../../model/schema.js';
6
7
  import { invalid } from '../diagnostic.js';
7
8
  import { compiledCallable } from './callable.js';
@@ -63,26 +64,30 @@ export function compiledClass(input, values, path) {
63
64
  }
64
65
  function classPolicies(input, kind, path) {
65
66
  const value = object(input, 'Class Policies', path);
66
- exact(value, [], 'Class Policies', path, ['read', 'traverse']);
67
- if (value.read !== undefined)
68
- classPolicy(value.read, kind, 'Class read Policy', `${path}/read`);
69
- if (value.traverse !== undefined)
70
- classPolicy(value.traverse, kind, 'Class traverse Policy', `${path}/traverse`);
67
+ exact(value, [], 'Class Policies', path, CLASS_OPERATIONS);
68
+ for (const operation of CLASS_OPERATIONS) {
69
+ const policy = value[operation];
70
+ if (policy === undefined)
71
+ continue;
72
+ const label = `Class ${operation} Policy`;
73
+ const slot = `${path}/${operation}`;
74
+ if (isRuleChecks(policy)) {
75
+ classRuleChecks(policy, slot, kind === 'node' && operation === 'create' ? 'create' : 'class');
76
+ continue;
77
+ }
78
+ // Each slot spells one constant, and a Node create rule has no Node for a named Policy alone.
79
+ if (policy === !isClassObservation(operation))
80
+ continue;
81
+ if (kind === 'node' && operation === 'create')
82
+ invalid(label, undefined, slot);
83
+ definitionRef(policy, 'policy', label, slot);
84
+ }
71
85
  }
72
- /**
73
- * Check one observation slot: `false`, the rule no caller satisfies, a named Policy, or, on a Node
74
- * Class only, Policy checks over the observed Node.
75
- */
76
- function classPolicy(input, kind, label, path) {
77
- if (kind === 'node' &&
78
- input !== null &&
86
+ /** Policy checks, as opposed to a constant or a named Policy. */
87
+ function isRuleChecks(input) {
88
+ return (input !== null &&
79
89
  typeof input === 'object' &&
80
- ['check', 'sameNode', 'allOf', 'anyOf'].some((key) => Object.hasOwn(input, key))) {
81
- classRuleChecks(input, path);
82
- return;
83
- }
84
- if (input !== false)
85
- definitionRef(input, 'policy', label, path);
90
+ ['check', 'sameNode', 'allOf', 'anyOf'].some((key) => Object.hasOwn(input, key)));
86
91
  }
87
92
  function edgeContract(value, path) {
88
93
  const constraintsPath = `${path}/constraints`;
@@ -4,7 +4,8 @@ export declare function compiledPolicy(input: JsonValue, path: string): never;
4
4
  /** Check one callable's caller rule: `true`, the rule every caller satisfies, or Policy checks. */
5
5
  export declare function callablePolicy(input: JsonValue | undefined, path: string): void;
6
6
  /**
7
- * Check one Node Class rule: Policy checks over `self` and `ref` objects only, in their one
8
- * spelling. A lone check of `self` is spelled as the Policy reference itself.
7
+ * Check one Class rule: Policy checks over `self` and `ref` objects only, or, in a Node create
8
+ * rule, `ref` objects only, in their one spelling. A lone check of `self` is spelled as the Policy
9
+ * reference itself.
9
10
  */
10
- export declare function classRuleChecks(input: JsonValue, path: string): void;
11
+ export declare function classRuleChecks(input: JsonValue, path: string, site: 'class' | 'create'): void;
@@ -130,17 +130,18 @@ export function callablePolicy(input, path) {
130
130
  policyCheck(input, 0, path);
131
131
  }
132
132
  /**
133
- * Check one Node Class rule: Policy checks over `self` and `ref` objects only, in their one
134
- * spelling. A lone check of `self` is spelled as the Policy reference itself.
133
+ * Check one Class rule: Policy checks over `self` and `ref` objects only, or, in a Node create
134
+ * rule, `ref` objects only, in their one spelling. A lone check of `self` is spelled as the Policy
135
+ * reference itself.
135
136
  */
136
- export function classRuleChecks(input, path) {
137
- policyCheck(input, 0, path, 'class');
137
+ export function classRuleChecks(input, path, site) {
138
+ policyCheck(input, 0, path, site);
138
139
  const rule = input;
139
140
  if (json.serialize(normalizeClassRule(rule)) !== json.serialize(rule))
140
141
  invalid('Class rule spelling', undefined, path);
141
142
  }
142
143
  /** What a site calls its checks in a refusal. */
143
- const SITE = { callable: 'Callable Policy', class: 'Class rule' };
144
+ const SITE = { callable: 'Callable Policy', class: 'Class rule', create: 'Create rule' };
144
145
  /** Check the recursive Policy check wire grammar of one use site. */
145
146
  function policyCheck(input, depth, path, site = 'callable') {
146
147
  const name = SITE[site];
@@ -148,7 +149,7 @@ function policyCheck(input, depth, path, site = 'callable') {
148
149
  invalid(`${name} depth`, undefined, path);
149
150
  const value = object(input, name, path);
150
151
  if (Object.hasOwn(value, 'sameNode')) {
151
- const equality = site === 'class' ? 'Class rule equality' : 'Callable equality';
152
+ const equality = site === 'callable' ? 'Callable equality' : `${name} equality`;
152
153
  exact(value, ['sameNode'], equality, path);
153
154
  const pair = object(value.sameNode, equality, `${path}/sameNode`);
154
155
  exact(pair, ['left', 'right'], `${equality} operands`, `${path}/sameNode`);
@@ -174,7 +175,7 @@ function policyCheck(input, depth, path, site = 'callable') {
174
175
  function policyCheckObject(input, objectPath, site) {
175
176
  const name = SITE[site];
176
177
  const target = object(input, `${name} object`, objectPath);
177
- if (target.kind === 'self')
178
+ if (target.kind === 'self' && site !== 'create')
178
179
  exact(target, ['kind'], `${name} self`, objectPath);
179
180
  else if (target.kind === 'input' && site === 'callable') {
180
181
  exact(target, ['kind', 'field'], `${name} input`, objectPath);
@@ -107,7 +107,7 @@ function programValidation(programs, properties, propertyOrdering, callables) {
107
107
  ...(callable.fields === undefined
108
108
  ? {}
109
109
  : { fields: Object.freeze(Object.keys(callable.fields)) }),
110
- validateInput: (value) => validateValueProgram(input, value),
110
+ validateInput: (value, options) => validateValueProgram(input, value, [input.root], options),
111
111
  validateInputField(field, value) {
112
112
  const nodes = callable.fields?.[field];
113
113
  return nodes === undefined
@@ -136,7 +136,7 @@ function createDefinitions(declarations, classesByKey, input) {
136
136
  policy: definition.policy,
137
137
  ...(inputFields === undefined ? {} : { inputFields }),
138
138
  definition,
139
- validateInput: (value) => input.validation.callableInput(key, value),
139
+ validateInput: (value, options) => input.validation.callableInput(key, value, options),
140
140
  validateInputField: (field, value) => input.validation.callableInputField(key, field, value),
141
141
  validateOutput: (value) => input.validation.callableOutput(key, value),
142
142
  });
@@ -1,3 +1,4 @@
1
+ import type { Options } from '@astrale-os/kernel-dsl/value';
1
2
  import type { Key as AddressKey, PropertyKey, Revision } from '../../addressing/index.js';
2
3
  import type { DomainDependency, DomainDependencyMap } from '../../addressing/index.js';
3
4
  import type { CoreEdge } from '../../language/core/model/core.js';
@@ -27,7 +28,7 @@ export interface DomainValidation {
27
28
  propertyProgram(key: PropertyKey): ValueProgram;
28
29
  propertyOrdering(key: PropertyKey): OrderingKind | undefined;
29
30
  callableExists(key: AddressKey.Callable): boolean;
30
- callableInput(key: AddressKey.Callable, input: unknown): readonly Diagnostic[];
31
+ callableInput(key: AddressKey.Callable, input: unknown, options?: Options): readonly Diagnostic[];
31
32
  callableFields(key: AddressKey.Callable): readonly string[] | undefined;
32
33
  callableInputField(key: AddressKey.Callable, field: string, input: unknown): readonly Diagnostic[];
33
34
  callableOutput(key: AddressKey.Callable, output: unknown): readonly Diagnostic[];
@@ -80,7 +80,7 @@ export function createClasses(definitions, validation) {
80
80
  executable: isMethodExecutable(definition, methodDefinition),
81
81
  ...(inputFields === undefined ? {} : { inputFields }),
82
82
  definition: methodDefinition,
83
- validateInput: (input) => validation.callableInput(methodKey, input),
83
+ validateInput: (input, options) => validation.callableInput(methodKey, input, options),
84
84
  validateInputField: (field, input) => validation.callableInputField(methodKey, field, input),
85
85
  validateOutput: (output) => validation.callableOutput(methodKey, output),
86
86
  });
@@ -1,3 +1,4 @@
1
+ import type { Options } from '@astrale-os/kernel-dsl/value';
1
2
  import type { Key, PropertyKey } from '../../addressing/index.js';
2
3
  import type { Diagnostic } from '../../language/validation/diagnostic.js';
3
4
  import type { ValueProgram } from '../../language/value/compile/model.js';
@@ -10,7 +11,7 @@ export interface PropertyValidation {
10
11
  }
11
12
  export interface CallableValidation {
12
13
  readonly fields?: readonly string[];
13
- validateInput(input: unknown): readonly Diagnostic[];
14
+ validateInput(input: unknown, options?: Options): readonly Diagnostic[];
14
15
  validateInputField(field: string, input: unknown): readonly Diagnostic[];
15
16
  validateOutput(output: unknown): readonly Diagnostic[];
16
17
  }
@@ -21,7 +21,7 @@ export function createDomainValidation(properties, callables) {
21
21
  propertyProgram: (key) => propertyOf(key).program(),
22
22
  propertyOrdering: (key) => properties.get(key)?.ordering,
23
23
  callableExists: (key) => callables.has(key),
24
- callableInput: (key, input) => callableOf(key).validateInput(input),
24
+ callableInput: (key, input, options) => callableOf(key).validateInput(input, options),
25
25
  callableFields: (key) => callables.get(key)?.fields,
26
26
  callableInputField: (key, field, input) => callableOf(key).validateInputField(field, input),
27
27
  callableOutput: (key, output) => callableOf(key).validateOutput(output),
@@ -5,7 +5,7 @@ export { isBinaryInput } from './language/callable/model/callable.js';
5
5
  export type { EventOf, StateMachine, StateOf } from './language/state/index.js';
6
6
  export { PropertyValues } from './language/index.js';
7
7
  export * as metadata from './language/definition/metadata.js';
8
- export type { AnyResolvedClass, BoundMethodOf, BoundMethodShortcutsOf, CallableResultOf, CallableInputOf, CallableInputModeOf, CallableOutputOf, ClassSatisfactionKeysOf, ClassDefinition, CoreDefinition, CoreEdge, FunctionAuth, FunctionDefinition, Inheritance, BinaryInput, BinaryInputProjection, CallableInputContract, MethodDefinition, MethodOwnerKeyOf, CallablePolicy, ClassPolicy, ClassRuleChecks, ClassRuleObject, NodeClassPolicy, PolicyCheckExpression, PolicyDefinition, PropertyImmutability, PropertyInputOf, PropertyAccessOf, PropertyOutputOf, PropertyShortcutsOf, PropertyValuesOf, ResolvedClass, ResolvedCoreDefinition, ResolvedCoreEndpoint, ResolvedData, ResolvedFunction, ExecutableMethod, ResolvedMethod, ResolvedMethodCollection, ResolvedPolicy, ResolvedProperty, ResolvedView, MethodAccessOf, SelfMethodsOf, SelfPropertiesOf, SelfStaticMethodsOf, StaticMethodsOf, ViewDefinition, } from './language/index.js';
8
+ export type { AnyResolvedClass, BoundMethodOf, BoundMethodShortcutsOf, CallableResultOf, CallableInputOf, CallableInputModeOf, CallableOutputOf, ClassSatisfactionKeysOf, ClassDefinition, CoreDefinition, CoreEdge, FunctionAuth, FunctionDefinition, Inheritance, BinaryInput, BinaryInputProjection, CallableInputContract, MethodDefinition, MethodOwnerKeyOf, CallablePolicy, ClassPolicy, ClassRuleChecks, ClassRuleObject, ClassWritePolicy, CreateRuleChecks, EdgeClassPolicy, EdgeClassWritePolicy, EdgeRuleChecks, NodeClassCreatePolicy, NodeClassPolicy, NodeClassWritePolicy, PolicyCheckExpression, PolicyDefinition, PropertyImmutability, PropertyInputOf, PropertyAccessOf, PropertyOutputOf, PropertyShortcutsOf, PropertyValuesOf, ResolvedClass, ResolvedCoreDefinition, ResolvedCoreEndpoint, ResolvedData, ResolvedFunction, ExecutableMethod, ResolvedMethod, ResolvedMethodCollection, ResolvedPolicy, ResolvedProperty, ResolvedView, MethodAccessOf, SelfMethodsOf, SelfPropertiesOf, SelfStaticMethodsOf, StaticMethodsOf, ViewDefinition, } from './language/index.js';
9
9
  export { acceptDefinitionRef, callableAddress, ClassKey, equalDefinitionRefs, isDefinitionRef, Key, MethodKey, PropertyKey, } from './addressing/index.js';
10
10
  export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, FunctionRef, MethodAddress, PolicyRef, Revision, ViewRef, } from './addressing/index.js';
11
11
  export type { Definition } from './language/index.js';
@@ -1,3 +1,4 @@
1
+ import type { Options } from '@astrale-os/kernel-dsl/value';
1
2
  import type { DefinitionKey, FunctionRef, Key, MethodKey } from '../../addressing/index.js';
2
3
  import type { ResolvedClass } from '../class/resolved.js';
3
4
  import type { CallablePolicy } from '../policy/model/policy.js';
@@ -36,7 +37,11 @@ export interface ResolvedMethod<out Input = unknown, out Output = unknown, out S
36
37
  readonly static: Static;
37
38
  readonly abstract: Abstract;
38
39
  };
39
- validateInput(input: unknown): readonly Diagnostic[];
40
+ /**
41
+ * The issues of `input` against the callable's input schema, `options` overriding the portable
42
+ * limits it is admitted under: those it crossed the wire under, for a call's input.
43
+ */
44
+ validateInput(input: unknown, options?: Options): readonly Diagnostic[];
40
45
  validateInputField(field: string, input: unknown): readonly Diagnostic[];
41
46
  validateOutput(output: unknown): readonly Diagnostic[];
42
47
  readonly __input?: Input;
@@ -62,7 +67,11 @@ export interface ResolvedFunction<out Input = unknown, out Output = unknown, out
62
67
  /** Present only when the callable input is a safely enumerable closed object. */
63
68
  readonly inputFields?: readonly string[];
64
69
  readonly definition: CallableDefinitionOf<FunctionDefinition, Auth, Policy>;
65
- validateInput(input: unknown): readonly Diagnostic[];
70
+ /**
71
+ * The issues of `input` against the callable's input schema, `options` overriding the portable
72
+ * limits it is admitted under: those it crossed the wire under, for a call's input.
73
+ */
74
+ validateInput(input: unknown, options?: Options): readonly Diagnostic[];
66
75
  validateInputField(field: string, input: unknown): readonly Diagnostic[];
67
76
  validateOutput(output: unknown): readonly Diagnostic[];
68
77
  readonly __input?: Input;
@@ -1,6 +1,6 @@
1
1
  import type { ClassRef, DefinitionBase, PropertyKey } from '../../../addressing/index.js';
2
2
  import type { MethodRecord } from '../../callable/model/callable.js';
3
- import type { ClassPolicy, NodeClassPolicy } from '../../policy/model/policy.js';
3
+ import type { EdgeClassPolicy, EdgeClassWritePolicy, NodeClassCreatePolicy, NodeClassPolicy, NodeClassWritePolicy } from '../../policy/model/policy.js';
4
4
  import type { DataFacet } from './data.js';
5
5
  import type { EdgeContract } from './edge.js';
6
6
  import type { EdgePropertyDefinition, NodePropertyDefinition, PropertyFacet } from './property.js';
@@ -10,15 +10,27 @@ export type ClassKind = 'node' | 'edge';
10
10
  * on that Domain; `'internal'`, those of its Domain only; `'final'`, none.
11
11
  */
12
12
  export type Inheritance = 'open' | 'internal' | 'final';
13
- /** An absent observation rule admits every caller; a delegation is still scoped by its ceiling. */
13
+ /**
14
+ * An absent observation rule admits every caller; a delegation is still scoped by its ceiling. An
15
+ * absent write rule admits only the Domain that declares the Class.
16
+ */
14
17
  export interface NodeClassPolicies {
15
18
  readonly read?: NodeClassPolicy;
16
19
  readonly traverse?: NodeClassPolicy;
20
+ readonly create?: NodeClassCreatePolicy;
21
+ readonly update?: NodeClassWritePolicy;
22
+ readonly delete?: NodeClassWritePolicy;
17
23
  }
18
- /** An absent observation rule admits every caller; a delegation is still scoped by its ceiling. */
24
+ /**
25
+ * An absent observation rule admits every caller; a delegation is still scoped by its ceiling. An
26
+ * absent write rule admits only the Domain that declares the Class.
27
+ */
19
28
  export interface EdgeClassPolicies {
20
- readonly read?: ClassPolicy;
21
- readonly traverse?: ClassPolicy;
29
+ readonly read?: EdgeClassPolicy;
30
+ readonly traverse?: EdgeClassPolicy;
31
+ readonly create?: EdgeClassWritePolicy;
32
+ readonly update?: EdgeClassWritePolicy;
33
+ readonly delete?: EdgeClassWritePolicy;
22
34
  }
23
35
  /**
24
36
  * The Properties one key of a Node Class names, own or inherited, which no two Nodes of the Classes
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Freeze `value` and every object it reaches, in place. An object already frozen is taken as frozen
3
+ * through, so a value built from frozen parts is frozen without walking them again.
4
+ */
5
+ export declare function deepFreeze<T>(value: T): Readonly<T>;
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Freeze `value` and every object it reaches, in place. An object already frozen is taken as frozen
3
+ * through, so a value built from frozen parts is frozen without walking them again.
4
+ */
1
5
  export function deepFreeze(value) {
2
6
  if (value === null || typeof value !== 'object' || Object.isFrozen(value)) {
3
7
  return value;
@@ -21,6 +21,7 @@ export type PolicyUseContract = {
21
21
  /** What Policy typing reads of the closure it types against: source or compiled admission. */
22
22
  export interface PolicyTypingScope {
23
23
  readonly normalized: ReadonlyMap<string, PolicyExpression>;
24
+ /** Each resolves, through `resolveClass`, to a Node Class. */
24
25
  readonly allNodeClasses: readonly ClassRef[];
25
26
  /** The concrete Classes of `allNodeClasses`, in its order. */
26
27
  readonly concreteNodeClasses: readonly ClassRef[];
@@ -16,9 +16,7 @@ export class PolicyTyping {
16
16
  // paths; for that to merge distinct variables, the Policy expands deeper than
17
17
  // `limits.policy.patternDepth`.
18
18
  #expansions = new Map();
19
- #nodeClasses;
20
- #concreteClasses;
21
- #satisfaction;
19
+ #index;
22
20
  constructor(scope) {
23
21
  this.scope = scope;
24
22
  }
@@ -31,9 +29,7 @@ export class PolicyTyping {
31
29
  }
32
30
  /** Whether the Policy accepts every concrete source and target pair the Edge Class accepts. */
33
31
  policyAcceptsEdgeClass(name, declaration) {
34
- const endpoints = declaration.orientation === 'directed'
35
- ? [declaration.endpoints.source, declaration.endpoints.target]
36
- : declaration.endpoints;
32
+ const endpoints = endpointPair(declaration);
37
33
  const sources = this.#concreteEndpointClasses(endpoints[0].accepts);
38
34
  const targets = this.#concreteEndpointClasses(endpoints[1].accepts);
39
35
  return sources.every((source) => targets.every((target) => this.#policyAcceptsEdge(name, { kind: 'exact', ref: source }, { kind: 'exact', ref: target })));
@@ -73,10 +69,13 @@ export class PolicyTyping {
73
69
  return branches !== undefined && branches.every((branch) => this.branchIsTyped(branch, use));
74
70
  }
75
71
  #concreteEndpointClasses(accepts) {
76
- this.#concreteClasses ??= new Set(this.scope.concreteNodeClasses.map(definitionRefIndex));
77
- const concrete = this.#concreteClasses;
72
+ const { concrete } = this.#nodeClassIndex();
78
73
  return this.#satisfyingOneOf(accepts).filter((ref) => concrete.has(definitionRefIndex(ref)));
79
74
  }
75
+ #nodeClassIndex() {
76
+ this.#index ??= nodeClassIndex(this.scope);
77
+ return this.#index;
78
+ }
80
79
  #expandedBranches(name, visiting, namespace) {
81
80
  if (visiting.has(name))
82
81
  return undefined;
@@ -164,11 +163,7 @@ export class PolicyTyping {
164
163
  if (contract.kind === 'exact')
165
164
  return this.#applyExact(candidates, contract.ref);
166
165
  if (contract.kind === 'default') {
167
- return uniqueCandidates(candidates.flatMap((candidate) => candidate.kind === 'exact'
168
- ? this.#isNodeClass(candidate.ref)
169
- ? [candidate]
170
- : []
171
- : this.#nodeCandidates(candidate)));
166
+ return this.#applyAnyNodeClass(candidates).filter((candidate) => candidate.kind !== 'exact' || this.#isNodeClass(candidate.ref));
172
167
  }
173
168
  return candidates;
174
169
  }
@@ -202,10 +197,10 @@ export class PolicyTyping {
202
197
  result.push({ kind: 'exact', ref: exact });
203
198
  }
204
199
  }
205
- return uniqueCandidates(result);
200
+ return uniqueBy(result, candidateKey);
206
201
  }
207
202
  #applyAnyNodeClass(candidates) {
208
- return uniqueCandidates(candidates.flatMap((candidate) => candidate.kind === 'exact' ? [candidate] : this.#nodeCandidates(candidate)));
203
+ return uniqueBy(candidates.flatMap((candidate) => candidate.kind === 'exact' ? [candidate] : this.#nodeCandidates(candidate)), candidateKey);
209
204
  }
210
205
  #applyRequirement(candidates, required) {
211
206
  const result = [];
@@ -220,11 +215,11 @@ export class PolicyTyping {
220
215
  else {
221
216
  result.push({
222
217
  kind: 'open',
223
- requirements: uniqueClasses([...candidate.requirements, required]),
218
+ requirements: uniqueBy([...candidate.requirements, required], definitionRefIndex),
224
219
  });
225
220
  }
226
221
  }
227
- return uniqueCandidates(result);
222
+ return uniqueBy(result, candidateKey);
228
223
  }
229
224
  #applyEndpoint(candidates, accepts) {
230
225
  const result = [];
@@ -242,11 +237,11 @@ export class PolicyTyping {
242
237
  for (const accept of accepts) {
243
238
  result.push({
244
239
  kind: 'open',
245
- requirements: uniqueClasses([...candidate.requirements, accept]),
240
+ requirements: uniqueBy([...candidate.requirements, accept], definitionRefIndex),
246
241
  });
247
242
  }
248
243
  }
249
- return uniqueCandidates(result);
244
+ return uniqueBy(result, candidateKey);
250
245
  }
251
246
  #classSatisfiesEndpoint(candidate, accepts) {
252
247
  return accepts.some((accept) => this.#classExtends(candidate, accept));
@@ -263,7 +258,7 @@ export class PolicyTyping {
263
258
  * `#classSatisfiesEndpoint` accepts, read from the index rather than tested one by one.
264
259
  */
265
260
  #satisfyingOneOf(accepts) {
266
- const index = (this.#satisfaction ??= satisfactionIndex(this.scope));
261
+ const index = this.#nodeClassIndex();
267
262
  const positions = new Set();
268
263
  for (const accept of accepts) {
269
264
  const required = this.scope.resolveClass(accept);
@@ -271,7 +266,7 @@ export class PolicyTyping {
271
266
  continue;
272
267
  if (isGraphRoot(required))
273
268
  return index.nodeClasses;
274
- for (const position of index.satisfying.get(ancestryKey(required.ref)) ?? []) {
269
+ for (const position of index.satisfying.get(definitionRefIndex(required.ref)) ?? []) {
275
270
  positions.add(position);
276
271
  }
277
272
  }
@@ -281,8 +276,7 @@ export class PolicyTyping {
281
276
  return lists.every((accepts) => this.#classSatisfiesEndpoint(ref, accepts));
282
277
  }
283
278
  #isNodeClass(ref) {
284
- this.#nodeClasses ??= new Set(this.scope.allNodeClasses.map(definitionRefIndex));
285
- return this.#nodeClasses.has(definitionRefIndex(ref));
279
+ return this.#nodeClassIndex().nodes.has(definitionRefIndex(ref));
286
280
  }
287
281
  /** The Node Classes an open or `node` candidate can be. */
288
282
  #nodeCandidates(candidate) {
@@ -307,16 +301,14 @@ export class PolicyTyping {
307
301
  return inhabited ? [{ kind: 'node', lists }] : [];
308
302
  }
309
303
  }
310
- /** Classes are satisfied by origin and name, as `classSatisfies` compares them. */
311
- function ancestryKey(ref) {
312
- return `${ref.origin}\u0000${ref.name}`;
313
- }
314
304
  /**
315
305
  * Index each Node Class of the closure under every Class its ancestry reaches, walked as
316
306
  * `classSatisfies` walks it: the Class itself, then each declared parent that resolves to a Node
317
307
  * Class. A Class then satisfies a non-root Node Class exactly when it is indexed under it.
308
+ * `classSatisfies` compares origin and name; every ClassRef has kind `class`, so the definition
309
+ * index of a ref keys it as that comparison does.
318
310
  */
319
- function satisfactionIndex(scope) {
311
+ function nodeClassIndex(scope) {
320
312
  const nodeClasses = [];
321
313
  const satisfying = new Map();
322
314
  for (const ref of scope.allNodeClasses) {
@@ -324,30 +316,38 @@ function satisfactionIndex(scope) {
324
316
  if (actual?.kind !== 'node')
325
317
  continue;
326
318
  const position = nodeClasses.push(ref) - 1;
327
- const seen = new Set();
328
- const keys = new Set();
329
- const pending = [actual];
330
- for (let value = pending.pop(); value !== undefined; value = pending.pop()) {
331
- if (seen.has(value))
332
- continue;
333
- seen.add(value);
334
- const key = ancestryKey(value.ref);
335
- if (!keys.has(key)) {
336
- keys.add(key);
337
- const positions = satisfying.get(key);
338
- if (positions === undefined)
339
- satisfying.set(key, [position]);
340
- else
341
- positions.push(position);
342
- }
343
- for (const parent of value.extends) {
344
- const resolved = scope.resolveClass(parent);
345
- if (resolved?.kind === 'node')
346
- pending.push(resolved);
347
- }
319
+ for (const key of ancestryKeys(actual, scope)) {
320
+ const positions = satisfying.get(key);
321
+ if (positions === undefined)
322
+ satisfying.set(key, [position]);
323
+ else
324
+ positions.push(position);
325
+ }
326
+ }
327
+ return {
328
+ nodeClasses,
329
+ nodes: new Set(nodeClasses.map(definitionRefIndex)),
330
+ concrete: new Set(scope.concreteNodeClasses.map(definitionRefIndex)),
331
+ satisfying,
332
+ };
333
+ }
334
+ /** The keys of the Node Classes a Node Class's ancestry reaches, itself included. */
335
+ function ancestryKeys(actual, scope) {
336
+ const seen = new Set();
337
+ const keys = new Set();
338
+ const pending = [actual];
339
+ for (let value = pending.pop(); value !== undefined; value = pending.pop()) {
340
+ if (seen.has(value))
341
+ continue;
342
+ seen.add(value);
343
+ keys.add(definitionRefIndex(value.ref));
344
+ for (const parent of value.extends) {
345
+ const resolved = scope.resolveClass(parent);
346
+ if (resolved?.kind === 'node')
347
+ pending.push(resolved);
348
348
  }
349
349
  }
350
- return { nodeClasses, satisfying };
350
+ return keys;
351
351
  }
352
352
  function targetContract(term, use) {
353
353
  if (use.kind === 'object')
@@ -378,9 +378,7 @@ function variantsForEdge(use, typing) {
378
378
  const declaration = typing.scope.edgeDeclaration(edge.class);
379
379
  if (declaration === undefined)
380
380
  return [];
381
- const endpoints = declaration.orientation === 'directed'
382
- ? [declaration.endpoints.source, declaration.endpoints.target]
383
- : declaration.endpoints;
381
+ const endpoints = endpointPair(declaration);
384
382
  const bounds = edge.repeat ?? { min: 1, max: 1 };
385
383
  if (!Number.isInteger(bounds.min) ||
386
384
  !Number.isInteger(bounds.max) ||
@@ -422,7 +420,13 @@ function variantsForEdge(use, typing) {
422
420
  });
423
421
  }
424
422
  }
425
- return uniqueEdgeVariants(variants);
423
+ return uniqueBy(variants, edgeVariantKey);
424
+ }
425
+ /** The two endpoints of an Edge Class, a directed one's as source then target. */
426
+ function endpointPair(declaration) {
427
+ return declaration.orientation === 'directed'
428
+ ? [declaration.endpoints.source, declaration.endpoints.target]
429
+ : declaration.endpoints;
426
430
  }
427
431
  function startEndpoint(endpoints, reversed) {
428
432
  return reversed ? endpoints[1] : endpoints[0];
@@ -430,31 +434,22 @@ function startEndpoint(endpoints, reversed) {
430
434
  function endEndpoint(endpoints, reversed) {
431
435
  return reversed ? endpoints[0] : endpoints[1];
432
436
  }
433
- function uniqueEdgeVariants(values) {
434
- const result = new Map();
435
- for (const value of values) {
436
- const key = `${value.constraints
437
- .map(([term, accepts]) => `${term}:${accepts.map(definitionRefIndex).sort().join(',')}`)
438
- .join('|')}=${value.equalities.map(([left, right]) => `${left}:${right}`).join('|')}`;
439
- result.set(key, value);
440
- }
441
- return [...result.values()];
437
+ /** The values with distinct keys, each at its key's first position and holding its last value. */
438
+ function uniqueBy(values, key) {
439
+ return [...new Map(values.map((value) => [key(value), value])).values()];
442
440
  }
443
- function uniqueCandidates(values) {
444
- const result = new Map();
445
- for (const value of values) {
446
- const key = value.kind === 'exact'
447
- ? `exact:${definitionRefIndex(value.ref)}`
448
- : value.kind === 'open'
449
- ? `open:${value.requirements.map(definitionRefIndex).sort().join('|')}`
450
- : `node:${value.lists
451
- .map((accepts) => accepts.map(definitionRefIndex).sort().join(','))
452
- .sort()
453
- .join('|')}`;
454
- result.set(key, value);
455
- }
456
- return [...result.values()];
441
+ function edgeVariantKey(variant) {
442
+ const constraints = variant.constraints.map(([term, accepts]) => `${term}:${classSetKey(accepts)}`);
443
+ const equalities = variant.equalities.map(([left, right]) => `${left}:${right}`);
444
+ return `${constraints.join('|')}=${equalities.join('|')}`;
445
+ }
446
+ function candidateKey(candidate) {
447
+ if (candidate.kind === 'exact')
448
+ return `exact:${definitionRefIndex(candidate.ref)}`;
449
+ if (candidate.kind === 'open')
450
+ return `open:${classSetKey(candidate.requirements)}`;
451
+ return `node:${candidate.lists.map(classSetKey).sort().join('|')}`;
457
452
  }
458
- function uniqueClasses(values) {
459
- return [...new Map(values.map((value) => [definitionRefIndex(value), value])).values()];
453
+ function classSetKey(refs) {
454
+ return refs.map(definitionRefIndex).sort().join(',');
460
455
  }
@@ -90,23 +90,63 @@ export type PolicyObjectRef = ClassRef | FunctionRef | PolicyRef | ViewRef | Cor
90
90
  export declare function policyObjectKey(ref: PolicyObjectRef): string;
91
91
  export declare function policyObjectOrigin(ref: PolicyObjectRef): string;
92
92
  export declare function projectedMemberClass(ref: Exclude<PolicyObjectRef, CoreRef>): ClassRef;
93
+ export type ClassObservation = 'read' | 'traverse';
94
+ export type ClassWrite = 'create' | 'update' | 'delete';
95
+ export type ClassOperation = ClassObservation | ClassWrite;
96
+ /**
97
+ * Every Class slot: the observations, which admit every caller when absent, then the writes, which
98
+ * admit only the Domain that declares the Class when absent.
99
+ */
100
+ export declare const CLASS_OPERATIONS: readonly ClassOperation[];
101
+ export declare function isClassObservation(operation: ClassOperation): operation is ClassObservation;
93
102
  /**
94
103
  * One Class observation slot: a named Policy, or `false`, the rule no caller satisfies. Its other
95
104
  * value has no spelling there: an absent observation Policy already means `true`.
96
105
  */
97
106
  export type ClassPolicy = PolicyRef | false;
98
- /** What a Node Class rule may check: the observed Node, or one exact projected member. */
107
+ /**
108
+ * One Class write slot: a named Policy, or `true`, the rule every Identity satisfies. Its other
109
+ * value has no spelling there: an absent write Policy already means the owning Domain only.
110
+ */
111
+ export type ClassWritePolicy = PolicyRef | true;
112
+ /** What a Class rule may check: the observed or written instance, or one exact projected member. */
99
113
  export type ClassRuleObject = Extract<PolicyCheckObject, {
100
114
  readonly kind: 'self' | 'ref';
101
115
  }>;
102
116
  /**
103
- * Policy checks deciding one Node Class observation. `self` is the observed Node; a `ref` object
104
- * is the same Node for every candidate. A lone check of `self` is spelled as its Policy reference.
117
+ * Policy checks deciding one Node Class slot. `self` is the observed or written Node; a `ref`
118
+ * object is the same Node for every candidate. A lone check of `self` is spelled as its Policy
119
+ * reference.
105
120
  */
106
121
  export type ClassRuleChecks = PolicyCheckExpression<PolicyRef, ClassRuleObject>;
122
+ /**
123
+ * Policy checks deciding one Edge Class slot. `self` is the observed or written Edge, checked by a
124
+ * Policy whose source and target are its endpoints; a `ref` object is one exact member, the same
125
+ * Node for every candidate. A lone check of `self` is spelled as its Policy reference.
126
+ */
127
+ export type EdgeRuleChecks = ClassRuleChecks;
128
+ /** What a Node create rule may check: one exact projected member, since the Node is not yet. */
129
+ export type CreateRuleObject = Extract<PolicyCheckObject, {
130
+ readonly kind: 'ref';
131
+ }>;
132
+ /** Policy checks deciding the creation of a Node: every object is a `ref` member. */
133
+ export type CreateRuleChecks = PolicyCheckExpression<PolicyRef, CreateRuleObject>;
107
134
  /** One Node Class observation slot: a Class Policy, or Policy checks over the observed Node. */
108
135
  export type NodeClassPolicy = ClassPolicy | ClassRuleChecks;
109
- export declare function isClassRuleChecks(value: ClassPolicy | ClassRuleChecks | undefined): value is ClassRuleChecks;
136
+ /** One Node Class update or delete slot: a write Policy, or Policy checks over the written Node. */
137
+ export type NodeClassWritePolicy = ClassWritePolicy | ClassRuleChecks;
138
+ /**
139
+ * The Node Class create slot: `true`, or Policy checks over members. A named Policy alone has no
140
+ * object there, so it has no spelling.
141
+ */
142
+ export type NodeClassCreatePolicy = true | CreateRuleChecks;
143
+ /** One Edge Class observation slot: a Class Policy, or Policy checks over the observed Edge. */
144
+ export type EdgeClassPolicy = ClassPolicy | EdgeRuleChecks;
145
+ /** One Edge Class write slot: a write Policy, or Policy checks over the written Edge. */
146
+ export type EdgeClassWritePolicy = ClassWritePolicy | EdgeRuleChecks;
147
+ /** The value of any Class slot. */
148
+ export type ClassSlotPolicy = PolicyRef | boolean | ClassRuleChecks;
149
+ export declare function isClassRuleChecks(value: ClassSlotPolicy | undefined): value is ClassRuleChecks;
110
150
  /**
111
151
  * Whether every way Policy checks can hold passes through a check, so that the caller decides each
112
152
  * admission. `sameNode` compares two objects and names no caller.