@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.
- package/dist/v1/builder/class/authoring.js +3 -2
- package/dist/v1/builder/class/input.d.ts +45 -14
- package/dist/v1/builder/index.d.ts +2 -1
- package/dist/v1/builder/lowering/class.d.ts +8 -0
- package/dist/v1/builder/lowering/class.js +40 -16
- package/dist/v1/builder/lowering/schema/compile.js +3 -3
- package/dist/v1/builder/policy/authoring.js +97 -88
- package/dist/v1/bundle/model.js +1 -1
- package/dist/v1/compiled/admission/accept.js +1 -7
- package/dist/v1/compiled/admission/semantics/class.js +2 -3
- package/dist/v1/compiled/admission/semantics/policy/parity.js +25 -13
- package/dist/v1/compiled/admission/wire/class.js +23 -18
- package/dist/v1/compiled/admission/wire/policy.d.ts +4 -3
- package/dist/v1/compiled/admission/wire/policy.js +8 -7
- package/dist/v1/compiled/loader/load.js +1 -1
- package/dist/v1/domain/create.js +1 -1
- package/dist/v1/domain/model/input.d.ts +2 -1
- package/dist/v1/domain/projection/classes.js +1 -1
- package/dist/v1/domain/validation/create.d.ts +2 -1
- package/dist/v1/domain/validation/create.js +1 -1
- package/dist/v1/index.d.ts +1 -1
- package/dist/v1/language/callable/resolved.d.ts +11 -2
- package/dist/v1/language/class/model/class.d.ts +17 -5
- package/dist/v1/language/freeze.d.ts +5 -0
- package/dist/v1/{schema/canonical → language}/freeze.js +4 -0
- package/dist/v1/language/policy/analysis/typing.d.ts +1 -0
- package/dist/v1/language/policy/analysis/typing.js +74 -79
- package/dist/v1/language/policy/model/policy.d.ts +44 -4
- package/dist/v1/language/policy/model/policy.js +14 -0
- package/dist/v1/language/policy/normalization/normalize.d.ts +6 -6
- package/dist/v1/language/policy/normalization/normalize.js +5 -5
- package/dist/v1/language/validation/rule.d.ts +18 -8
- package/dist/v1/language/validation/rule.js +2 -11
- package/dist/v1/language/value/evaluate/program.d.ts +2 -1
- package/dist/v1/language/value/evaluate/program.js +2 -2
- package/dist/v1/language/value/evaluate/validate.d.ts +2 -2
- package/dist/v1/language/value/evaluate/validate.js +2 -2
- package/dist/v1/language/value/profile/json.validate.d.ts +3 -1
- package/dist/v1/language/value/profile/json.validate.js +3 -2
- package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +180 -25
- package/dist/v1/schema/admission/callable.js +1 -1
- package/dist/v1/schema/canonical/revision.js +4 -4
- package/dist/v1/schema/compatibility/comparators/callables.js +1 -1
- package/dist/v1/schema/compatibility/comparators/classes.js +4 -4
- package/dist/v1/schema/compatibility/comparators/members.js +1 -1
- package/dist/v1/schema/compatibility/comparators/values.js +2 -2
- package/dist/v1/schema/compatibility/report.js +1 -1
- package/dist/v1/schema/compatibility/versioning/index.js +1 -1
- package/dist/v1/schema/model/schema.js +1 -1
- package/dist/v1/schema/normalization/domain.js +9 -8
- package/dist/v1/schema/resolution/domain.js +1 -1
- package/dist/v1/schema/resolution/origins.js +3 -2
- package/dist/v1/schema/rule.d.ts +8 -20
- package/dist/v1/schema/rule.js +1 -1
- package/dist/v1/schema/validation/class.js +2 -3
- package/dist/v1/schema/validation/domain.json-schema.d.ts +2 -2
- package/dist/v1/schema/validation/domain.json-schema.js +20 -6
- package/dist/v1/schema/validation/domain.structure.d.ts +4765 -1005
- package/dist/v1/schema/validation/domain.structure.js +21 -9
- package/dist/v1/schema/validation/json.json-schema.d.ts +18 -2
- package/dist/v1/schema/validation/json.json-schema.js +14 -4
- package/dist/v1/schema/validation/policy/budgets.d.ts +1 -1
- package/dist/v1/schema/validation/policy/budgets.js +4 -4
- package/dist/v1/schema/validation/policy/uses.d.ts +10 -2
- package/dist/v1/schema/validation/policy/uses.js +26 -13
- package/dist/v1/schema/validation/policy/validate.js +2 -3
- package/dist/v1/schema/validation/value.json-schema.d.ts +4 -6
- package/dist/v1/schema/validation/value.json-schema.js +18 -25
- package/package.json +1 -1
- package/dist/v1/schema/canonical/domain.d.ts +0 -3
- package/dist/v1/schema/canonical/domain.digest.d.ts +0 -3
- package/dist/v1/schema/canonical/domain.digest.js +0 -5
- package/dist/v1/schema/canonical/domain.js +0 -5
- 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,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
|
8
|
-
* spelling. A lone check of `self` is spelled as the Policy
|
|
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
|
|
134
|
-
* spelling. A lone check of `self` is spelled as the Policy
|
|
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,
|
|
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 === '
|
|
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
|
package/dist/v1/domain/create.js
CHANGED
|
@@ -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),
|
package/dist/v1/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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?:
|
|
21
|
-
readonly traverse?:
|
|
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
|
|
@@ -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
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
200
|
+
return uniqueBy(result, candidateKey);
|
|
206
201
|
}
|
|
207
202
|
#applyAnyNodeClass(candidates) {
|
|
208
|
-
return
|
|
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:
|
|
218
|
+
requirements: uniqueBy([...candidate.requirements, required], definitionRefIndex),
|
|
224
219
|
});
|
|
225
220
|
}
|
|
226
221
|
}
|
|
227
|
-
return
|
|
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:
|
|
240
|
+
requirements: uniqueBy([...candidate.requirements, accept], definitionRefIndex),
|
|
246
241
|
});
|
|
247
242
|
}
|
|
248
243
|
}
|
|
249
|
-
return
|
|
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 =
|
|
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(
|
|
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.#
|
|
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
|
|
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
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
459
|
-
return
|
|
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
|
-
/**
|
|
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
|
|
104
|
-
* is the same Node for every candidate. A lone check of `self` is spelled as its Policy
|
|
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
|
-
|
|
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.
|