@astrale-os/kernel-dsl 0.2.0-beta.44 → 0.2.0-beta.46
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/addressing/class-key.js +5 -4
- package/dist/v1/addressing/method-key.js +3 -2
- package/dist/v1/builder/lowering/value/zod.js +11 -2
- package/dist/v1/builder/value/full-match.js +6 -3
- package/dist/v1/compiled/admission/accept.js +27 -6
- package/dist/v1/compiled/admission/semantics.js +41 -16
- package/dist/v1/compiled/loader/load.js +1 -0
- package/dist/v1/domain/model/input.d.ts +3 -0
- package/dist/v1/domain/projection/classes.js +1 -0
- package/dist/v1/domain/validation/create.d.ts +2 -0
- package/dist/v1/domain/validation/create.js +12 -0
- package/dist/v1/language/class/resolved.d.ts +3 -0
- package/dist/v1/language/index.d.ts +2 -0
- package/dist/v1/language/index.js +1 -0
- package/dist/v1/language/policy/normalization/normalize.d.ts +4 -0
- package/dist/v1/language/policy/normalization/normalize.js +51 -13
- package/dist/v1/language/value/inclusion/includes.d.ts +9 -0
- package/dist/v1/language/value/inclusion/includes.js +777 -0
- package/dist/v1/language/value/profile/rules.js +11 -32
- package/dist/v1/language/value/regexp/ast.js +3 -64
- package/dist/v1/language/value/regexp/compile.d.ts +25 -2
- package/dist/v1/language/value/regexp/compile.js +48 -12
- package/dist/v1/language/value/regexp/syntax.d.ts +11 -1
- package/dist/v1/language/value/regexp/syntax.js +14 -2
- package/dist/v1/language/value/regexp/unicode.d.ts +32 -0
- package/dist/v1/language/value/regexp/unicode.js +139 -0
- package/dist/v1/language/value/syntax/full-match.js +6 -3
- package/dist/v1/schema/compatibility/comparators/callables.d.ts +7 -0
- package/dist/v1/schema/compatibility/comparators/callables.js +13 -0
- package/dist/v1/schema/compatibility/comparators/classes.d.ts +18 -0
- package/dist/v1/schema/compatibility/comparators/classes.js +54 -0
- package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
- package/dist/v1/schema/compatibility/comparators/index.js +14 -2
- package/dist/v1/schema/compatibility/evidence.d.ts +8 -1
- package/dist/v1/schema/compatibility/evidence.js +10 -1
- package/dist/v1/schema/compatibility/meaning/footprint.js +3 -1
- package/dist/v1/schema/compatibility/meaning/index.js +4 -2
- package/dist/v1/schema/compatibility/model.d.ts +11 -6
- package/dist/v1/schema/compatibility/references.d.ts +8 -1
- package/dist/v1/schema/compatibility/references.js +39 -6
- package/dist/v1/schema/compatibility/scope.d.ts +5 -0
- package/dist/v1/schema/compatibility/scope.js +5 -2
- package/dist/v1/schema/compatibility/structure/index.js +21 -2
- package/dist/v1/schema/compatibility/structure/requirements.js +26 -1
- package/dist/v1/schema/compatibility/subjects.js +20 -2
- package/dist/v1/schema/resolution/domain.js +1 -0
- package/dist/v1/schema/resolution/origins.d.ts +11 -4
- package/dist/v1/schema/resolution/origins.js +7 -8
- package/dist/v1/schema/validation/class.d.ts +10 -0
- package/dist/v1/schema/validation/class.js +4 -2
- package/dist/v1/schema/validation/policy/budgets.js +16 -3
- package/dist/v1/schema/validation/policy/typing.js +15 -0
- package/package.json +1 -1
|
@@ -20,10 +20,11 @@ export function ClassKey(input) {
|
|
|
20
20
|
function is(input) {
|
|
21
21
|
if (typeof input !== 'string' || !Key.is(input))
|
|
22
22
|
return false;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
!
|
|
23
|
+
// Only the coordinate is tested: an origin label may itself be `property` or `method`.
|
|
24
|
+
const coordinate = input.slice(input.indexOf(':') + 1);
|
|
25
|
+
return (coordinate.startsWith('class.') &&
|
|
26
|
+
!coordinate.includes('.property.') &&
|
|
27
|
+
!coordinate.includes('.method.'));
|
|
27
28
|
}
|
|
28
29
|
ClassKey.is = is;
|
|
29
30
|
function ref(input) {
|
|
@@ -21,8 +21,9 @@ export function MethodKey(input) {
|
|
|
21
21
|
function is(input) {
|
|
22
22
|
if (typeof input !== 'string' || !Key.is(input))
|
|
23
23
|
return false;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// The member kind is the third segment: the owner Class may itself be named `method`.
|
|
25
|
+
const [kind, , member] = input.slice(input.indexOf(':') + 1).split('.');
|
|
26
|
+
return kind === 'class' && member === 'method';
|
|
26
27
|
}
|
|
27
28
|
MethodKey.is = is;
|
|
28
29
|
function address(input) {
|
|
@@ -161,7 +161,10 @@ function inspectCheck(def, pointer, diagnostics) {
|
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
163
|
if (def.check === 'string_format' && def.format === 'regex' && def.pattern instanceof RegExp) {
|
|
164
|
-
if (
|
|
164
|
+
if (def.pattern.flags === '') {
|
|
165
|
+
diagnostics.push(diagnostic('ZOD_SCHEMA_NOT_LOSSLESS', pointer, 'Zod RegExp without the u flag reads \\p{...} as literal text, and classes and quantifiers by UTF-16 code unit, unlike the V1 I-Regexp it would lower to; add the u flag.'));
|
|
166
|
+
}
|
|
167
|
+
else if (!isLosslessRegexp(def.pattern)) {
|
|
165
168
|
diagnostics.push(diagnostic('ZOD_SCHEMA_NOT_LOSSLESS', pointer, 'Zod RegExp flags, anchors, dot semantics, or syntax cannot be preserved as a V1 I-Regexp substring pattern.'));
|
|
166
169
|
}
|
|
167
170
|
}
|
|
@@ -292,8 +295,14 @@ function escapePointerToken(value) {
|
|
|
292
295
|
function isPlainRecord(value) {
|
|
293
296
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
294
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Whether a Zod RegExp search means its source read as an I-Regexp. Only the u flag qualifies:
|
|
300
|
+
* without it, `\p{L}` matches the text `p{L}`, and classes and quantifiers apply to UTF-16 code
|
|
301
|
+
* units rather than scalars; `v` reads class syntax differently, and every other flag changes the
|
|
302
|
+
* match itself.
|
|
303
|
+
*/
|
|
295
304
|
function isLosslessRegexp(pattern) {
|
|
296
|
-
if (pattern.flags !== '
|
|
305
|
+
if (pattern.flags !== 'u')
|
|
297
306
|
return false;
|
|
298
307
|
if (!isIRegexp(pattern.source))
|
|
299
308
|
return false;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { refuseIRegexp, stateBudgetMessage } from '../../language/value/regexp/syntax.js';
|
|
2
2
|
import { fullMatch as languageFullMatch, } from '../../language/value/syntax/full-match.js';
|
|
3
3
|
import { fail } from '../schema/diagnostic.js';
|
|
4
4
|
export { fullMatchSource } from '../../language/value/syntax/full-match.js';
|
|
5
5
|
/** Authoring facade preserving the Builder-owned diagnostic for invalid source syntax. */
|
|
6
6
|
export function fullMatch(source) {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const refusal = refuseIRegexp(source);
|
|
8
|
+
if (refusal !== undefined) {
|
|
9
|
+
fail('BUILDER_FULL_MATCH_INVALID', '/fullMatch', refusal === 'syntax'
|
|
10
|
+
? 'fullMatch(...) requires a complete RFC 9485 I-Regexp source.'
|
|
11
|
+
: stateBudgetMessage('fullMatch(...)'));
|
|
9
12
|
}
|
|
10
13
|
return languageFullMatch(source);
|
|
11
14
|
}
|
|
@@ -8,6 +8,8 @@ import { admitStateTopology } from '../../language/state/topology.js';
|
|
|
8
8
|
import { bindValueProgram } from '../../language/value/compile/load.js';
|
|
9
9
|
import { isValueObject } from '../../language/value/model/object.js';
|
|
10
10
|
import { classifyValueProgramOrdering } from '../../language/value/ordering.js';
|
|
11
|
+
import { compilePattern } from '../../language/value/regexp/compile.js';
|
|
12
|
+
import { isIRegexp } from '../../language/value/regexp/syntax.js';
|
|
11
13
|
import { invalidCompiled } from '../diagnostic.js';
|
|
12
14
|
import { revisionOfCompiledEntry } from './revision.js';
|
|
13
15
|
import { validateCompiledSemantics } from './semantics.js';
|
|
@@ -24,7 +26,8 @@ export function acceptCompiledSchema(input) {
|
|
|
24
26
|
if (root.version !== 2)
|
|
25
27
|
invalid('version', undefined, '/version');
|
|
26
28
|
const rootPin = dependency(root.root, 'root', '/root');
|
|
27
|
-
const
|
|
29
|
+
const machines = new Map();
|
|
30
|
+
const closure = array(root.closure, 'closure', '/closure').map((value, index) => entry(value, index, `/closure/${index}`, machines));
|
|
28
31
|
if (closure.length > dependencyLimits.closure) {
|
|
29
32
|
invalid('dependency closure limit', 'COMPILED_DEPENDENCY_LIMITS', '/closure');
|
|
30
33
|
}
|
|
@@ -74,7 +77,7 @@ export function acceptCompiledSchema(input) {
|
|
|
74
77
|
validateCompiledSemantics(candidate);
|
|
75
78
|
return deepFreeze(candidate);
|
|
76
79
|
}
|
|
77
|
-
function entry(input, entryIndex, path) {
|
|
80
|
+
function entry(input, entryIndex, path, machines) {
|
|
78
81
|
const value = object(input, `closure[${entryIndex}]`, path);
|
|
79
82
|
exact(value, ['origin', 'revision', 'dependencies', 'definitions', 'facts', 'values', 'patterns'], 'entry', path);
|
|
80
83
|
const origin = originValue(value.origin, 'entry origin', `${path}/origin`);
|
|
@@ -89,7 +92,7 @@ function entry(input, entryIndex, path) {
|
|
|
89
92
|
}
|
|
90
93
|
const valuesPath = `${path}/values`;
|
|
91
94
|
const patternsPath = `${path}/patterns`;
|
|
92
|
-
const patterns = array(value.patterns, 'entry patterns', patternsPath).map((item, index) => pattern(item, index, `${patternsPath}/${index}
|
|
95
|
+
const patterns = array(value.patterns, 'entry patterns', patternsPath).map((item, index) => pattern(item, index, `${patternsPath}/${index}`, machines));
|
|
93
96
|
const values = array(value.values, 'values', valuesPath).map((item, index) => program(item, index, `${valuesPath}/${index}`, patterns));
|
|
94
97
|
const bound = values.map((value) => bindValueProgram(value, patterns));
|
|
95
98
|
const definitionsPath = `${path}/definitions`;
|
|
@@ -838,10 +841,28 @@ function valueNode(input, nodeIndex, path) {
|
|
|
838
841
|
: { reference: integer(value.reference, 'reference', `${path}/reference`) }),
|
|
839
842
|
};
|
|
840
843
|
}
|
|
841
|
-
|
|
844
|
+
/**
|
|
845
|
+
* Admit one received pattern as data and return the machine compiled from its source. A received
|
|
846
|
+
* machine never decides membership: it may come from another compiler, from a host with other
|
|
847
|
+
* Unicode data, or from a forger, and the artifact carries no compiler version that could tell a
|
|
848
|
+
* current machine from those, so it is ignored rather than compared. Its source decides, through
|
|
849
|
+
* the same bounded `isIRegexp` and `compilePattern` that source resolution uses.
|
|
850
|
+
*/
|
|
851
|
+
function pattern(input, patternIndex, path, machines) {
|
|
842
852
|
const value = object(input, `patterns[${patternIndex}]`, path);
|
|
843
|
-
|
|
844
|
-
|
|
853
|
+
const { source } = Object.hasOwn(value, 'runs')
|
|
854
|
+
? runPattern(value, path)
|
|
855
|
+
: nfaPattern(value, path);
|
|
856
|
+
let machine = machines.get(source);
|
|
857
|
+
if (machine === undefined) {
|
|
858
|
+
if (!isIRegexp(source))
|
|
859
|
+
invalid('Pattern source', 'COMPILED_SOURCE_INVALID', `${path}/source`);
|
|
860
|
+
machine = compilePattern(source);
|
|
861
|
+
machines.set(source, machine);
|
|
862
|
+
}
|
|
863
|
+
return machine;
|
|
864
|
+
}
|
|
865
|
+
function nfaPattern(value, path) {
|
|
845
866
|
exact(value, ['source', 'start', 'states'], 'Pattern', path);
|
|
846
867
|
if (typeof value.source !== 'string')
|
|
847
868
|
invalid('Pattern source', undefined, `${path}/source`);
|
|
@@ -21,10 +21,11 @@ export function validateCompiledSemantics(candidate) {
|
|
|
21
21
|
const partial = index(candidate);
|
|
22
22
|
const analyses = analyzeClasses(partial.classes);
|
|
23
23
|
const context = { ...partial, analyses };
|
|
24
|
+
const admitted = admittedOrigins(candidate);
|
|
24
25
|
validateClassFacets(context);
|
|
25
|
-
validateEdges(context);
|
|
26
|
+
validateEdges(context, admitted);
|
|
26
27
|
validateViews(candidate, context);
|
|
27
|
-
validatePolicies(candidate, context);
|
|
28
|
+
validatePolicies(candidate, context, admitted);
|
|
28
29
|
validateCore(candidate, context);
|
|
29
30
|
}
|
|
30
31
|
function index(candidate) {
|
|
@@ -171,7 +172,30 @@ function validateClassFacets(context) {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
|
-
|
|
175
|
+
/**
|
|
176
|
+
* The origins each closure entry was admitted against: itself and its transitive dependencies.
|
|
177
|
+
* Source admission decides undirected overlap and types Policies within that closure alone, so a
|
|
178
|
+
* dependent's Class extending a dependency's Classes never reaches the dependency's own decisions.
|
|
179
|
+
*/
|
|
180
|
+
function admittedOrigins(candidate) {
|
|
181
|
+
const entries = new Map(candidate.closure.map((entry) => [entry.origin, entry]));
|
|
182
|
+
const scopes = new Map();
|
|
183
|
+
const scope = (origin) => {
|
|
184
|
+
const known = scopes.get(origin);
|
|
185
|
+
if (known !== undefined)
|
|
186
|
+
return known;
|
|
187
|
+
const origins = new Set([origin]);
|
|
188
|
+
for (const dependency of Object.values(entries.get(origin)?.dependencies ?? {}))
|
|
189
|
+
for (const reached of scope(dependency.origin))
|
|
190
|
+
origins.add(reached);
|
|
191
|
+
scopes.set(origin, origins);
|
|
192
|
+
return origins;
|
|
193
|
+
};
|
|
194
|
+
for (const entry of candidate.closure)
|
|
195
|
+
scope(entry.origin);
|
|
196
|
+
return scopes;
|
|
197
|
+
}
|
|
198
|
+
function validateEdges(context, admitted) {
|
|
175
199
|
for (const located of context.classes.values()) {
|
|
176
200
|
if (!isCompiledEdge(located.value))
|
|
177
201
|
continue;
|
|
@@ -194,7 +218,7 @@ function validateEdges(context) {
|
|
|
194
218
|
}
|
|
195
219
|
if (edge.orientation === 'undirected' &&
|
|
196
220
|
!sameIncidentEndpoint(edge.endpoints[0], edge.endpoints[1]) &&
|
|
197
|
-
undirectedOverlap(context, edge.endpoints)) {
|
|
221
|
+
undirectedOverlap(context, edge.endpoints, admitted.get(located.entry.origin))) {
|
|
198
222
|
fail(`${located.pointer}/endpoints`, 'Unequal undirected endpoint contracts overlap.');
|
|
199
223
|
}
|
|
200
224
|
for (const [parentIndex, parentRef] of edge.extends.entries()) {
|
|
@@ -242,8 +266,9 @@ function sameIncidentEndpoint(left, right) {
|
|
|
242
266
|
a.length === b.length &&
|
|
243
267
|
a.every((value, index) => value === b[index]));
|
|
244
268
|
}
|
|
245
|
-
function undirectedOverlap(context, endpoints) {
|
|
246
|
-
return [...context.classes.values()].some(({ value }) => value.
|
|
269
|
+
function undirectedOverlap(context, endpoints, origins) {
|
|
270
|
+
return [...context.classes.values()].some(({ value }) => origins.has(value.ref.origin) &&
|
|
271
|
+
value.kind === 'node' &&
|
|
247
272
|
!value.abstract &&
|
|
248
273
|
classSatisfiesAny(context, value.ref, endpoints[0].accepts) &&
|
|
249
274
|
classSatisfiesAny(context, value.ref, endpoints[1].accepts));
|
|
@@ -274,14 +299,14 @@ function validateViews(candidate, context) {
|
|
|
274
299
|
}
|
|
275
300
|
}
|
|
276
301
|
}
|
|
277
|
-
function validatePolicies(candidate, context) {
|
|
302
|
+
function validatePolicies(candidate, context, admitted) {
|
|
278
303
|
for (const [entryIndex, entry] of candidate.closure.entries()) {
|
|
279
304
|
const localPolicies = new Map(entry.definitions.policies.map((value) => [value.ref.name, value]));
|
|
280
305
|
for (const [policyIndex, policy] of entry.definitions.policies.entries()) {
|
|
281
306
|
validatePolicyExpression(context, entry, policy.expression, `/closure/${entryIndex}/definitions/policies/${policyIndex}/expression`, localPolicies);
|
|
282
307
|
}
|
|
283
308
|
validatePolicyCycles(entry, localPolicies, entryIndex);
|
|
284
|
-
validatePolicyParity(context, entry, entryIndex, localPolicies);
|
|
309
|
+
validatePolicyParity(context, entry, entryIndex, localPolicies, admitted.get(entry.origin));
|
|
285
310
|
for (const [functionIndex, callable] of entry.definitions.functions.entries()) {
|
|
286
311
|
if (callable.policy !== undefined)
|
|
287
312
|
validateCallablePolicy(context, entry, callable.policy, typeof callable.input === 'number' ? callable.input : callable.input.metadata, callable.fields, false, `/closure/${entryIndex}/definitions/functions/${functionIndex}/policy`);
|
|
@@ -455,9 +480,9 @@ function isNodeClassRef(context, value) {
|
|
|
455
480
|
}
|
|
456
481
|
return context.classes.get(Key.of(ref))?.value.kind === 'node';
|
|
457
482
|
}
|
|
458
|
-
function validatePolicyParity(context, entry, entryIndex, policies) {
|
|
483
|
+
function validatePolicyParity(context, entry, entryIndex, policies, origins) {
|
|
459
484
|
const normalized = new Map([...policies].map(([name, policy]) => [name, normalizePolicyExpression(policy.expression)]));
|
|
460
|
-
const typing = new CompiledPolicyTyping(context, entry, normalized);
|
|
485
|
+
const typing = new CompiledPolicyTyping(context, entry, normalized, origins);
|
|
461
486
|
for (const [name, declaration] of policies) {
|
|
462
487
|
const path = `/closure/${entryIndex}/definitions/policies/${declarationIndex(entry, 'policies', name)}`;
|
|
463
488
|
if ('match' in declaration.expression) {
|
|
@@ -602,21 +627,21 @@ function nodePathAccepts(entry, input, field) {
|
|
|
602
627
|
? annotation.accepts
|
|
603
628
|
: [];
|
|
604
629
|
}
|
|
630
|
+
/** Types one entry's Policies against the Node Classes of its own closure, as source admission. */
|
|
605
631
|
class CompiledPolicyTyping {
|
|
606
632
|
context;
|
|
607
633
|
entry;
|
|
608
634
|
normalized;
|
|
609
635
|
allNodeClasses;
|
|
610
636
|
concreteNodeClasses;
|
|
611
|
-
constructor(context, entry, normalized) {
|
|
637
|
+
constructor(context, entry, normalized, origins) {
|
|
612
638
|
this.context = context;
|
|
613
639
|
this.entry = entry;
|
|
614
640
|
this.normalized = normalized;
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
.filter(({ value }) => value.kind === 'node' && !value.abstract)
|
|
641
|
+
const nodeClasses = [...context.classes.values()].filter(({ value }) => value.kind === 'node' && origins.has(value.ref.origin));
|
|
642
|
+
this.allNodeClasses = nodeClasses.map(({ value }) => value.ref);
|
|
643
|
+
this.concreteNodeClasses = nodeClasses
|
|
644
|
+
.filter(({ value }) => !value.abstract)
|
|
620
645
|
.map(({ value }) => value.ref);
|
|
621
646
|
}
|
|
622
647
|
expandedBranches(name, visiting, namespace) {
|
|
@@ -125,6 +125,7 @@ function programValidation(programs, properties, propertyOrdering, callables) {
|
|
|
125
125
|
propertyValidation.set(key, Object.freeze({
|
|
126
126
|
ordering: propertyOrdering.get(key),
|
|
127
127
|
validate: (value) => validateValueProgram(program, value),
|
|
128
|
+
program: () => program,
|
|
128
129
|
}));
|
|
129
130
|
}
|
|
130
131
|
const callableValidation = new Map();
|
|
@@ -3,6 +3,7 @@ import type { DomainDependency, DomainDependencyMap } from '../../addressing/ind
|
|
|
3
3
|
import type { CoreEdge } from '../../language/core/model/core.js';
|
|
4
4
|
import type { Definition } from '../../language/definition.js';
|
|
5
5
|
import type { Diagnostic } from '../../language/validation/diagnostic.js';
|
|
6
|
+
import type { ValueProgram } from '../../language/value/compile/model.js';
|
|
6
7
|
import type { OrderingKind } from '../../language/value/ordering.js';
|
|
7
8
|
/** One admitted Schema entry in a dependency-closed Domain universe. */
|
|
8
9
|
export interface DomainEntryInput {
|
|
@@ -22,6 +23,8 @@ export interface DomainInput {
|
|
|
22
23
|
export interface DomainValidation {
|
|
23
24
|
propertyExists(key: PropertyKey): boolean;
|
|
24
25
|
property(key: PropertyKey, input: unknown): readonly Diagnostic[];
|
|
26
|
+
/** The admitted program `property` evaluates. */
|
|
27
|
+
propertyProgram(key: PropertyKey): ValueProgram;
|
|
25
28
|
propertyOrdering(key: PropertyKey): OrderingKind | undefined;
|
|
26
29
|
callableExists(key: AddressKey.Callable): boolean;
|
|
27
30
|
callableInput(key: AddressKey.Callable, input: unknown): readonly Diagnostic[];
|
|
@@ -45,6 +45,7 @@ export function createClasses(definitions, validation) {
|
|
|
45
45
|
definition: propertyDefinition,
|
|
46
46
|
...(ordering === undefined ? {} : { ordering }),
|
|
47
47
|
validate: (input) => validation.property(propertyKey, input),
|
|
48
|
+
program: () => validation.propertyProgram(propertyKey),
|
|
48
49
|
});
|
|
49
50
|
ownProperties.push(property);
|
|
50
51
|
effectiveProperties.set(property.key, property);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Key, PropertyKey } from '../../addressing/index.js';
|
|
2
2
|
import type { Diagnostic } from '../../language/validation/diagnostic.js';
|
|
3
|
+
import type { ValueProgram } from '../../language/value/compile/model.js';
|
|
3
4
|
import type { OrderingKind } from '../../language/value/ordering.js';
|
|
4
5
|
import type { DomainValidation } from '../model/input.js';
|
|
5
6
|
export interface PropertyValidation {
|
|
6
7
|
readonly ordering?: OrderingKind;
|
|
7
8
|
validate(input: unknown): readonly Diagnostic[];
|
|
9
|
+
program(): ValueProgram;
|
|
8
10
|
}
|
|
9
11
|
export interface CallableValidation {
|
|
10
12
|
readonly fields?: readonly string[];
|
|
@@ -6,6 +6,12 @@ export function createDomainValidation(properties, callables) {
|
|
|
6
6
|
const target = properties.get(key);
|
|
7
7
|
return target === undefined ? unknownTarget('Property', key) : target.validate(input);
|
|
8
8
|
},
|
|
9
|
+
propertyProgram(key) {
|
|
10
|
+
const target = properties.get(key);
|
|
11
|
+
if (target === undefined)
|
|
12
|
+
throw new TypeError(`Property ${key} is absent.`);
|
|
13
|
+
return target.program();
|
|
14
|
+
},
|
|
9
15
|
propertyOrdering: (key) => properties.get(key)?.ordering,
|
|
10
16
|
callableExists: (key) => callables.has(key),
|
|
11
17
|
callableInput(key, input) {
|
|
@@ -33,6 +39,12 @@ export function combineDomainValidation(values) {
|
|
|
33
39
|
const owner = values.find((value) => value.propertyExists(key));
|
|
34
40
|
return owner === undefined ? unknownTarget('Property', key) : owner.property(key, input);
|
|
35
41
|
},
|
|
42
|
+
propertyProgram(key) {
|
|
43
|
+
const owner = values.find((value) => value.propertyExists(key));
|
|
44
|
+
if (owner === undefined)
|
|
45
|
+
throw new TypeError(`Property ${key} is absent.`);
|
|
46
|
+
return owner.propertyProgram(key);
|
|
47
|
+
},
|
|
36
48
|
propertyOrdering: (key) => values.find((value) => value.propertyExists(key))?.propertyOrdering(key),
|
|
37
49
|
callableExists: (key) => values.some((value) => value.callableExists(key)),
|
|
38
50
|
callableInput(key, input) {
|
|
@@ -3,6 +3,7 @@ import type { ClassRef, DefinitionKey, Key, PropertyKey } from '../../addressing
|
|
|
3
3
|
import type { ResolvedMethodCollection } from '../callable/resolved.js';
|
|
4
4
|
import type { StateTopology } from '../state/index.js';
|
|
5
5
|
import type { Diagnostic } from '../validation/diagnostic.js';
|
|
6
|
+
import type { ValueProgram } from '../value/compile/model.js';
|
|
6
7
|
import type { OrderingKind } from '../value/ordering.js';
|
|
7
8
|
import type { ClassDefinition, ClassKind } from './model/class.js';
|
|
8
9
|
import type { DataDefinition } from './model/data.js';
|
|
@@ -19,6 +20,8 @@ export interface ResolvedProperty<out Value = PortableValue, out Required extend
|
|
|
19
20
|
/** The one portable scalar ordering domain, when the Property is orderable. */
|
|
20
21
|
readonly ordering?: OrderingKind;
|
|
21
22
|
validate(input: unknown): readonly Diagnostic[];
|
|
23
|
+
/** The admitted program `validate` evaluates. */
|
|
24
|
+
program(): ValueProgram;
|
|
22
25
|
readonly __value?: Value;
|
|
23
26
|
readonly __input?: Input;
|
|
24
27
|
}
|
|
@@ -4,6 +4,8 @@ export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, F
|
|
|
4
4
|
export type { Value as PropertyValue } from '@astrale-os/kernel-dsl/value';
|
|
5
5
|
export type { JsonSchema as ValueSchema, JsonSchemaObject as ValueSchemaObject, } from './value/model/schema.js';
|
|
6
6
|
export type { OrderingKind } from './value/ordering.js';
|
|
7
|
+
export type { ValueProgram } from './value/compile/model.js';
|
|
8
|
+
export { valueProgramIncludes } from './value/inclusion/includes.js';
|
|
7
9
|
export { stateMachine } from './state/index.js';
|
|
8
10
|
export type { EventOf, StateMachine, StateOf, StateTopology } from './state/index.js';
|
|
9
11
|
export type * from './callable/model/callable.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { isMethodExecutable } from './callable/executable.js';
|
|
2
2
|
export { isBinaryInput } from './callable/model/callable.js';
|
|
3
|
+
export { valueProgramIncludes } from './value/inclusion/includes.js';
|
|
3
4
|
export { stateMachine } from './state/index.js';
|
|
4
5
|
export { PropertyValues } from './class/property-values.js';
|
|
5
6
|
export { policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './policy/model/policy.js';
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { PolicyCheckExpression, PolicyExpression, PolicyPatternExpression } from '../model/policy.js';
|
|
2
2
|
export declare function normalizePolicyExpression(value: PolicyExpression): PolicyExpression;
|
|
3
|
+
/**
|
|
4
|
+
* The fixed point of the single pass: a Pattern the single pass reproduces is returned unchanged,
|
|
5
|
+
* so the canonical form of every Pattern that was already stable keeps its bytes.
|
|
6
|
+
*/
|
|
3
7
|
export declare function normalizePolicyPattern(value: PolicyPatternExpression): PolicyPatternExpression;
|
|
4
8
|
export declare function normalizePolicyChecks(value: PolicyCheckExpression): PolicyCheckExpression;
|
|
@@ -10,7 +10,32 @@ export function normalizePolicyExpression(value) {
|
|
|
10
10
|
}
|
|
11
11
|
return { anyOf: uniqueRefs(value.anyOf) };
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Passes after the first, while one still changes the Pattern. A pass numbers variables by the
|
|
15
|
+
* operands as written, then merges duplicate operands and flattens nested ones, so its result can
|
|
16
|
+
* number differently when normalized again; the next pass numbers that result by itself. One
|
|
17
|
+
* further pass reached the fixed point on every Pattern tested, the other is margin, and a
|
|
18
|
+
* Pattern still changing keeps its last pass rather than failing.
|
|
19
|
+
*/
|
|
20
|
+
const SETTLING_PASSES = 2;
|
|
21
|
+
/**
|
|
22
|
+
* The fixed point of the single pass: a Pattern the single pass reproduces is returned unchanged,
|
|
23
|
+
* so the canonical form of every Pattern that was already stable keeps its bytes.
|
|
24
|
+
*/
|
|
13
25
|
export function normalizePolicyPattern(value) {
|
|
26
|
+
let current = normalizePatternOnce(value);
|
|
27
|
+
let encoding = json.serialize(current);
|
|
28
|
+
for (let pass = 0; pass < SETTLING_PASSES; pass++) {
|
|
29
|
+
const next = normalizePatternOnce(current);
|
|
30
|
+
const nextEncoding = json.serialize(next);
|
|
31
|
+
if (nextEncoding === encoding)
|
|
32
|
+
return current;
|
|
33
|
+
current = next;
|
|
34
|
+
encoding = nextEncoding;
|
|
35
|
+
}
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
38
|
+
function normalizePatternOnce(value) {
|
|
14
39
|
const rewritten = rewriteVariables(value, canonicalVariableIds(value));
|
|
15
40
|
return normalizePatternNode(rewritten);
|
|
16
41
|
}
|
|
@@ -71,7 +96,8 @@ function normalizePatternNode(value) {
|
|
|
71
96
|
}
|
|
72
97
|
function canonicalVariableIds(value) {
|
|
73
98
|
const declarations = new Map();
|
|
74
|
-
|
|
99
|
+
const depths = new Map();
|
|
100
|
+
collectDeclarations(value, declarations, depths);
|
|
75
101
|
const uses = new Map([...declarations.keys()].map((id) => [id, []]));
|
|
76
102
|
collectUses(value, uses);
|
|
77
103
|
let colors = declarations;
|
|
@@ -89,8 +115,15 @@ function canonicalVariableIds(value) {
|
|
|
89
115
|
break;
|
|
90
116
|
colors = next;
|
|
91
117
|
}
|
|
118
|
+
// Colors ignore where a variable is declared, so two variables can tie on color although
|
|
119
|
+
// different `exists` declare them, and which of them is numbered first decides which `exists`
|
|
120
|
+
// declares the smaller id. Their declaration depth tells them apart where it differs: operands of
|
|
121
|
+
// equal signature are ordered by their signature over depth-scoped colors before authored order,
|
|
122
|
+
// and the final order compares depth before the authored id (decision
|
|
123
|
+
// `policy-pattern-canonical-ties`). Both keys only order what color and first use leave tied.
|
|
124
|
+
const scoped = new Map([...colors].map(([id, color]) => [id, `${color}@${depths.get(id) ?? 0}`]));
|
|
92
125
|
const firstUse = new Map();
|
|
93
|
-
collectFirstUseKeys(value, colors, firstUse);
|
|
126
|
+
collectFirstUseKeys(value, colors, scoped, firstUse);
|
|
94
127
|
const ordered = [...declarations.keys()].sort((left, right) => {
|
|
95
128
|
const byColor = compareUtf8Bytes(colors.get(left) ?? '', colors.get(right) ?? '');
|
|
96
129
|
if (byColor !== 0)
|
|
@@ -98,26 +131,31 @@ function canonicalVariableIds(value) {
|
|
|
98
131
|
const byUse = compareUtf8Bytes(firstUse.get(left) ?? '', firstUse.get(right) ?? '');
|
|
99
132
|
if (byUse !== 0)
|
|
100
133
|
return byUse;
|
|
101
|
-
|
|
102
|
-
|
|
134
|
+
const byDepth = (depths.get(left) ?? 0) - (depths.get(right) ?? 0);
|
|
135
|
+
if (byDepth !== 0)
|
|
136
|
+
return byDepth;
|
|
137
|
+
// A remaining tie keeps the authored order of the ids: such variables were interchangeable in
|
|
138
|
+
// every admissible Pattern checked (`__tests__/canonical.test.ts`).
|
|
103
139
|
return left - right;
|
|
104
140
|
});
|
|
105
141
|
return new Map(ordered.map((id, index) => [id, index]));
|
|
106
142
|
}
|
|
107
|
-
|
|
143
|
+
/** Each declared variable's selector signature and the number of `exists` around its declaration. */
|
|
144
|
+
function collectDeclarations(value, result, depths, depth = 0) {
|
|
108
145
|
if ('exists' in value) {
|
|
109
146
|
for (const node of value.exists.nodes) {
|
|
110
147
|
result.set(node.variable.id, selectorSignature(node));
|
|
148
|
+
depths.set(node.variable.id, depth + 1);
|
|
111
149
|
}
|
|
112
|
-
collectDeclarations(value.exists.where, result);
|
|
150
|
+
collectDeclarations(value.exists.where, result, depths, depth + 1);
|
|
113
151
|
return;
|
|
114
152
|
}
|
|
115
153
|
if ('allOf' in value)
|
|
116
154
|
for (const child of value.allOf)
|
|
117
|
-
collectDeclarations(child, result);
|
|
155
|
+
collectDeclarations(child, result, depths, depth);
|
|
118
156
|
if ('anyOf' in value)
|
|
119
157
|
for (const child of value.anyOf)
|
|
120
|
-
collectDeclarations(child, result);
|
|
158
|
+
collectDeclarations(child, result, depths, depth);
|
|
121
159
|
}
|
|
122
160
|
function collectUses(value, result) {
|
|
123
161
|
if ('sameNode' in value) {
|
|
@@ -155,7 +193,7 @@ function termColor(term, colors) {
|
|
|
155
193
|
return `ref:${term.ref.kind === 'method' ? policyObjectKey(term.ref) : definitionRefIndex(term.ref)}`;
|
|
156
194
|
return term.kind;
|
|
157
195
|
}
|
|
158
|
-
function collectFirstUseKeys(value, colors, result, prefix = '$') {
|
|
196
|
+
function collectFirstUseKeys(value, colors, scoped, result, prefix = '$') {
|
|
159
197
|
if ('sameNode' in value) {
|
|
160
198
|
for (const term of [value.sameNode.left, value.sameNode.right]) {
|
|
161
199
|
if (term.kind !== 'variable')
|
|
@@ -182,15 +220,15 @@ function collectFirstUseKeys(value, colors, result, prefix = '$') {
|
|
|
182
220
|
return;
|
|
183
221
|
}
|
|
184
222
|
if ('exists' in value) {
|
|
185
|
-
collectFirstUseKeys(value.exists.where, colors, result, `${prefix}:exists`);
|
|
223
|
+
collectFirstUseKeys(value.exists.where, colors, scoped, result, `${prefix}:exists`);
|
|
186
224
|
return;
|
|
187
225
|
}
|
|
188
226
|
const key = 'allOf' in value ? 'allOf' : 'anyOf';
|
|
189
227
|
const operands = 'allOf' in value ? value.allOf : value.anyOf;
|
|
190
228
|
const children = operands
|
|
191
|
-
.map((child) => [patternSignature(child, colors), child])
|
|
192
|
-
.sort(([left], [right]) => compareUtf8Bytes(left, right));
|
|
193
|
-
children.forEach(([, child], index) => collectFirstUseKeys(child, colors, result, `${prefix}:${key}:${index}`));
|
|
229
|
+
.map((child) => [patternSignature(child, colors), patternSignature(child, scoped), child])
|
|
230
|
+
.sort(([left, leftScoped], [right, rightScoped]) => compareUtf8Bytes(left, right) || compareUtf8Bytes(leftScoped, rightScoped));
|
|
231
|
+
children.forEach(([, , child], index) => collectFirstUseKeys(child, colors, scoped, result, `${prefix}:${key}:${index}`));
|
|
194
232
|
}
|
|
195
233
|
function patternSignature(value, colors) {
|
|
196
234
|
if ('sameNode' in value)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ValueProgram } from '../compile/model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether `outer` admits every value `inner` admits, under the one V1 evaluator.
|
|
4
|
+
*
|
|
5
|
+
* `true` is a proof. `false` only means that no rule proved inclusion: a caller must then decide
|
|
6
|
+
* the values themselves. Each rule relates one assertion of `outer` to evidence carried by
|
|
7
|
+
* `inner`; a finite `inner` is decided exactly by evaluating its candidates.
|
|
8
|
+
*/
|
|
9
|
+
export declare function valueProgramIncludes(outer: ValueProgram, inner: ValueProgram): boolean;
|