@astrale-os/kernel-dsl 0.2.0-beta.36 → 0.2.0-beta.38
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/core/authoring.d.ts +7 -2
- package/dist/v1/builder/lowering/schema/context.d.ts +9 -4
- package/dist/v1/builder/lowering/schema/context.js +20 -20
- package/dist/v1/builder/policy/authoring.d.ts +6 -6
- package/dist/v1/builder/policy/authoring.js +3 -1
- package/dist/v1/compiled/admission/accept.js +97 -18
- package/dist/v1/compiled/admission/semantics.js +77 -24
- package/dist/v1/compiled/compiler/compile.js +23 -7
- package/dist/v1/compiled/loader/load.d.ts +11 -2
- package/dist/v1/compiled/loader/load.js +4 -1
- package/dist/v1/compiled/model/schema.d.ts +5 -4
- package/dist/v1/domain/create.js +17 -9
- package/dist/v1/index.d.ts +1 -1
- package/dist/v1/language/class/property-values.js +5 -2
- package/dist/v1/language/core/model/core.d.ts +7 -1
- package/dist/v1/language/core/resolved.d.ts +6 -2
- package/dist/v1/language/index.d.ts +1 -1
- package/dist/v1/language/index.js +1 -1
- package/dist/v1/language/policy/index.d.ts +1 -1
- package/dist/v1/language/policy/index.js +1 -1
- package/dist/v1/language/policy/model/policy.d.ts +1 -0
- package/dist/v1/language/policy/model/policy.js +17 -0
- package/dist/v1/language/value/compile/fields.d.ts +1 -1
- package/dist/v1/language/value/compile/load.d.ts +4 -2
- package/dist/v1/language/value/compile/load.js +22 -1
- package/dist/v1/language/value/compile/model.d.ts +16 -1
- package/dist/v1/language/value/evaluate/assertions.d.ts +2 -0
- package/dist/v1/language/value/evaluate/assertions.js +13 -0
- package/dist/v1/language/value/evaluate/evaluator.js +12 -7
- package/dist/v1/language/value/evaluate/preparation.d.ts +10 -1
- package/dist/v1/language/value/evaluate/preparation.js +17 -26
- package/dist/v1/language/value/evaluate/program.js +1 -1
- package/dist/v1/language/value/evaluate/validate.d.ts +6 -2
- package/dist/v1/language/value/evaluate/validate.js +18 -11
- package/dist/v1/language/value/ordering.d.ts +2 -0
- package/dist/v1/language/value/ordering.js +4 -8
- package/dist/v1/language/value/profile/json.validate.js +1 -1
- package/dist/v1/language/value/regexp/compile.d.ts +3 -3
- package/dist/v1/language/value/regexp/compile.js +56 -11
- package/dist/v1/language/value/regexp/matcher.d.ts +3 -3
- package/dist/v1/language/value/regexp/matcher.js +75 -3
- package/dist/v1/schema/compatibility/footprint.js +6 -5
- package/dist/v1/schema/normalization/domain.js +15 -1
- package/dist/v1/schema/resolution/domain.js +17 -10
- package/dist/v1/schema/resolution/origins.d.ts +3 -0
- package/dist/v1/schema/resolution/origins.js +12 -4
- package/dist/v1/schema/validation/core.js +78 -26
- package/dist/v1/schema/validation/domain.structure.js +7 -2
- package/dist/value/accept.d.ts +9 -0
- package/dist/value/accept.js +78 -5
- package/dist/value/canonical.js +2 -2
- package/dist/value/immutable.optimization.d.ts +12 -0
- package/dist/value/immutable.optimization.js +28 -0
- package/dist/value/index.d.ts +1 -1
- package/dist/value/index.js +1 -1
- package/dist/value/unicode.d.ts +1 -1
- package/dist/value/unicode.js +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { acceptForRead as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { diagnostic } from '../../validation/diagnostic.js';
|
|
3
3
|
export function validateCanonicalJsonDomain(value, pointer) {
|
|
4
4
|
try {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { Pattern } from '../compile/model.js';
|
|
2
|
-
/** Compile
|
|
3
|
-
export declare function compilePattern(source: string): Pattern;
|
|
1
|
+
import type { Pattern, RunPattern } from '../compile/model.js';
|
|
2
|
+
/** Compile scalar sequences as counted runs; preserve the NFA for branching expressions. */
|
|
3
|
+
export declare function compilePattern(source: string): Pattern | RunPattern;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { parseIRegexp } from './ast.js';
|
|
2
|
-
/** Compile
|
|
2
|
+
/** Compile scalar sequences as counted runs; preserve the NFA for branching expressions. */
|
|
3
3
|
export function compilePattern(source) {
|
|
4
4
|
const expression = parseIRegexp(source);
|
|
5
5
|
if (expression === undefined)
|
|
6
6
|
throw new TypeError('Cannot compile an invalid I-Regexp source.');
|
|
7
|
+
const runs = scalarRuns(expression);
|
|
8
|
+
if (runs !== undefined)
|
|
9
|
+
return Object.freeze({ source, runs });
|
|
7
10
|
const builder = new Builder();
|
|
8
11
|
const fragment = builder.expression(expression);
|
|
9
12
|
builder.states[fragment.end].accepting = true;
|
|
@@ -13,9 +16,45 @@ export function compilePattern(source) {
|
|
|
13
16
|
states: Object.freeze(builder.states.map(freezeState)),
|
|
14
17
|
});
|
|
15
18
|
}
|
|
19
|
+
function scalarRuns(expression) {
|
|
20
|
+
if (expression.alternatives.length !== 1)
|
|
21
|
+
return undefined;
|
|
22
|
+
const pieces = expression.alternatives[0].pieces;
|
|
23
|
+
let counted = false;
|
|
24
|
+
for (let index = 0; index < pieces.length; index++) {
|
|
25
|
+
const piece = pieces[index];
|
|
26
|
+
if (piece.atom.kind !== 'ranges' ||
|
|
27
|
+
(index !== pieces.length - 1 && piece.minimum !== piece.maximum))
|
|
28
|
+
return undefined;
|
|
29
|
+
if (piece.maximum !== undefined && piece.maximum > 1)
|
|
30
|
+
counted = true;
|
|
31
|
+
}
|
|
32
|
+
if (!counted)
|
|
33
|
+
return undefined;
|
|
34
|
+
const runs = [];
|
|
35
|
+
for (const piece of pieces) {
|
|
36
|
+
if (piece.atom.kind !== 'ranges')
|
|
37
|
+
return undefined;
|
|
38
|
+
runs.push(Object.freeze({
|
|
39
|
+
ranges: freezeRanges(piece.atom.ranges),
|
|
40
|
+
minimum: piece.minimum,
|
|
41
|
+
...(piece.maximum === undefined ? {} : { maximum: piece.maximum }),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
return Object.freeze(runs);
|
|
45
|
+
}
|
|
46
|
+
function freezeRanges(ranges) {
|
|
47
|
+
if (Object.isFrozen(ranges))
|
|
48
|
+
return ranges;
|
|
49
|
+
for (const range of ranges)
|
|
50
|
+
Object.freeze(range);
|
|
51
|
+
return Object.freeze(ranges);
|
|
52
|
+
}
|
|
16
53
|
class Builder {
|
|
17
54
|
states = [];
|
|
18
55
|
expression(expression) {
|
|
56
|
+
if (expression.alternatives.length === 1)
|
|
57
|
+
return this.sequence(expression.alternatives[0]);
|
|
19
58
|
const start = this.state();
|
|
20
59
|
const end = this.state();
|
|
21
60
|
for (const alternative of expression.alternatives) {
|
|
@@ -26,20 +65,26 @@ class Builder {
|
|
|
26
65
|
return { start, end };
|
|
27
66
|
}
|
|
28
67
|
sequence(sequence) {
|
|
29
|
-
let output
|
|
30
|
-
for (const piece of sequence.pieces)
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
let output;
|
|
69
|
+
for (const piece of sequence.pieces) {
|
|
70
|
+
const fragment = this.piece(piece);
|
|
71
|
+
output = output === undefined ? fragment : this.concat(output, fragment);
|
|
72
|
+
}
|
|
73
|
+
return output ?? this.empty();
|
|
33
74
|
}
|
|
34
75
|
piece(piece) {
|
|
35
|
-
let output
|
|
76
|
+
let output;
|
|
36
77
|
for (let index = 0; index < piece.minimum; index++) {
|
|
37
|
-
|
|
78
|
+
const fragment = this.atom(piece.atom);
|
|
79
|
+
output = output === undefined ? fragment : this.concat(output, fragment);
|
|
80
|
+
}
|
|
81
|
+
if (piece.maximum === undefined) {
|
|
82
|
+
const repeated = this.star(piece.atom);
|
|
83
|
+
return output === undefined ? repeated : this.concat(output, repeated);
|
|
38
84
|
}
|
|
39
|
-
if (piece.maximum === undefined)
|
|
40
|
-
return this.concat(output, this.star(piece.atom));
|
|
41
85
|
if (piece.maximum === piece.minimum)
|
|
42
|
-
return output;
|
|
86
|
+
return output ?? this.empty();
|
|
87
|
+
output ??= this.empty();
|
|
43
88
|
const end = this.state();
|
|
44
89
|
for (let index = piece.minimum; index < piece.maximum; index++) {
|
|
45
90
|
// A shorter repetition exits the whole suffix, rather than skipping arbitrary copies.
|
|
@@ -87,7 +132,7 @@ class Builder {
|
|
|
87
132
|
function freezeState(state) {
|
|
88
133
|
return Object.freeze({
|
|
89
134
|
epsilon: Object.freeze(state.epsilon),
|
|
90
|
-
transitions: Object.freeze(state.transitions.map((transition) => Object.freeze({ to: transition.to, ranges:
|
|
135
|
+
transitions: Object.freeze(state.transitions.map((transition) => Object.freeze({ to: transition.to, ranges: freezeRanges(transition.ranges) }))),
|
|
91
136
|
...(state.accepting === true ? { accepting: true } : {}),
|
|
92
137
|
});
|
|
93
138
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { Pattern } from '../compile/model.js';
|
|
1
|
+
import type { Pattern, RunPattern } from '../compile/model.js';
|
|
2
2
|
export interface IRegexpMatcher {
|
|
3
3
|
test(value: string): boolean;
|
|
4
4
|
matches(value: string): boolean;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Load the portable Thompson NFA executor
|
|
7
|
+
* Load the portable counted-run or Thompson NFA executor for search and whole-string matching.
|
|
8
8
|
* Scratch belongs to one invocation: no match-result cache, shared mutable cursor or DFA expansion.
|
|
9
9
|
*/
|
|
10
|
-
export declare function loadPattern(pattern: Pattern): IRegexpMatcher;
|
|
10
|
+
export declare function loadPattern(pattern: Pattern | RunPattern): IRegexpMatcher;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Load the portable Thompson NFA executor
|
|
2
|
+
* Load the portable counted-run or Thompson NFA executor for search and whole-string matching.
|
|
3
3
|
* Scratch belongs to one invocation: no match-result cache, shared mutable cursor or DFA expansion.
|
|
4
4
|
*/
|
|
5
5
|
export function loadPattern(pattern) {
|
|
6
|
+
if ('runs' in pattern)
|
|
7
|
+
return loadRuns(pattern);
|
|
6
8
|
const states = pattern.states;
|
|
7
9
|
const start = initialFrontier(pattern);
|
|
8
10
|
const run = (input, search) => {
|
|
@@ -52,8 +54,8 @@ export function loadPattern(pattern) {
|
|
|
52
54
|
const scalar = character.codePointAt(0);
|
|
53
55
|
for (let index = 0; index < activeCount; index++) {
|
|
54
56
|
for (const transition of active[index].state.transitions) {
|
|
55
|
-
for (const
|
|
56
|
-
if (scalar <
|
|
57
|
+
for (const range of transition.ranges) {
|
|
58
|
+
if (scalar < range[0] || scalar > range[1])
|
|
57
59
|
continue;
|
|
58
60
|
add(transition.to);
|
|
59
61
|
break;
|
|
@@ -83,6 +85,76 @@ export function loadPattern(pattern) {
|
|
|
83
85
|
matches: (input) => run(input, false),
|
|
84
86
|
});
|
|
85
87
|
}
|
|
88
|
+
function loadRuns(pattern) {
|
|
89
|
+
const runs = pattern.runs;
|
|
90
|
+
const run = (input, search) => {
|
|
91
|
+
if (search && runs.length === 1) {
|
|
92
|
+
const scalarRun = runs[0];
|
|
93
|
+
if (scalarRun.minimum === 0)
|
|
94
|
+
return true;
|
|
95
|
+
let count = 0;
|
|
96
|
+
for (const character of input) {
|
|
97
|
+
count = includesScalar(scalarRun.ranges, character.codePointAt(0)) ? count + 1 : 0;
|
|
98
|
+
if (count >= scalarRun.minimum)
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
let start = 0;
|
|
104
|
+
do {
|
|
105
|
+
let offset = start;
|
|
106
|
+
let accepted = true;
|
|
107
|
+
for (let index = 0; index < runs.length; index++) {
|
|
108
|
+
const scalarRun = runs[index];
|
|
109
|
+
const maximum = search && index === runs.length - 1 ? scalarRun.minimum : (scalarRun.maximum ?? Infinity);
|
|
110
|
+
let count = 0;
|
|
111
|
+
while (count < maximum && offset < input.length) {
|
|
112
|
+
const scalar = input.codePointAt(offset);
|
|
113
|
+
if (!includesScalar(scalarRun.ranges, scalar))
|
|
114
|
+
break;
|
|
115
|
+
offset += scalar > 0xffff ? 2 : 1;
|
|
116
|
+
count++;
|
|
117
|
+
}
|
|
118
|
+
if (count < scalarRun.minimum) {
|
|
119
|
+
accepted = false;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (accepted && (search || offset === input.length))
|
|
124
|
+
return true;
|
|
125
|
+
if (!search || start === input.length)
|
|
126
|
+
return false;
|
|
127
|
+
start += input.codePointAt(start) > 0xffff ? 2 : 1;
|
|
128
|
+
} while (start <= input.length);
|
|
129
|
+
return false;
|
|
130
|
+
};
|
|
131
|
+
return Object.freeze({
|
|
132
|
+
test: (input) => run(input, true),
|
|
133
|
+
matches: (input) => run(input, false),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function includesScalar(ranges, scalar) {
|
|
137
|
+
if (ranges.length < 8) {
|
|
138
|
+
for (const range of ranges) {
|
|
139
|
+
if (scalar >= range[0] && scalar <= range[1])
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
let low = 0;
|
|
145
|
+
let high = ranges.length - 1;
|
|
146
|
+
while (low <= high) {
|
|
147
|
+
const middle = (low + high) >>> 1;
|
|
148
|
+
const range = ranges[middle];
|
|
149
|
+
if (scalar < range[0])
|
|
150
|
+
high = middle - 1;
|
|
151
|
+
else if (scalar > range[1])
|
|
152
|
+
low = middle + 1;
|
|
153
|
+
else
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
86
158
|
function initialFrontier(pattern) {
|
|
87
159
|
const start = pattern.start;
|
|
88
160
|
const seen = new Set([start]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Key } from '../../addressing/index.js';
|
|
2
2
|
import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
3
|
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
4
|
-
import { collectCallableContractRefs, collectCallableRefs, collectClassPolicyRefs, collectClassRefs, collectCoreRefs, collectEndpointRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, } from '../resolution/origins.js';
|
|
4
|
+
import { collectCallableContractRefs, collectCallableRefs, collectClassPolicyRefs, collectClassRefs, collectCoreRefs, collectEndpointRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, coreEndpointDefinitionRef, } from '../resolution/origins.js';
|
|
5
5
|
import { contextInvalid, effectiveDefinition, effectiveMethod, requireOwner } from './context.js';
|
|
6
6
|
import { classCoordinate, keyReference, levelOf, memberCoordinate, methodCoordinate, propertyCoordinate, } from './coordinate.js';
|
|
7
7
|
import { classMeaning, fingerprintMeaning, meaningForCoordinate, memberMeaning, methodMeaning, propertyMeaning, } from './meaning.js';
|
|
@@ -221,10 +221,11 @@ function collectMemberSemanticRefs(owner, ref, add) {
|
|
|
221
221
|
if (!incident)
|
|
222
222
|
continue;
|
|
223
223
|
add(edge.class);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
for (const endpoint of [edge.source, edge.target]) {
|
|
225
|
+
const endpointRef = coreEndpointDefinitionRef(endpoint);
|
|
226
|
+
if (endpointRef !== undefined)
|
|
227
|
+
add(endpointRef);
|
|
228
|
+
}
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
}
|
|
@@ -175,7 +175,21 @@ function compareCoreEndpoints(left, right) {
|
|
|
175
175
|
return right.kind === 'domain' ? 0 : -1;
|
|
176
176
|
if (right.kind === 'domain')
|
|
177
177
|
return 1;
|
|
178
|
-
|
|
178
|
+
const [a, b] = [coreEndpointOrder(left), coreEndpointOrder(right)];
|
|
179
|
+
return (compareUnicode(a.origin, b.origin) ||
|
|
180
|
+
compareUnicode(a.kind, b.kind) ||
|
|
181
|
+
compareUnicode(a.name, b.name) ||
|
|
182
|
+
compareUnicode(a.member, b.member));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A Method orders under its owner Class through the synthetic kind `method`; every other endpoint
|
|
186
|
+
* keeps the exact `compareDefinitionRefs` tuple with an empty member, so the canonical order of
|
|
187
|
+
* already-authored Core Edges — and therefore their revisions — never moves.
|
|
188
|
+
*/
|
|
189
|
+
function coreEndpointOrder(ref) {
|
|
190
|
+
return ref.kind === 'method'
|
|
191
|
+
? { origin: ref.owner.origin, kind: ref.kind, name: ref.owner.name, member: ref.name }
|
|
192
|
+
: { origin: ref.origin, kind: ref.kind, name: ref.name, member: '' };
|
|
179
193
|
}
|
|
180
194
|
function compareBytes(left, right) {
|
|
181
195
|
const encoder = new TextEncoder();
|
|
@@ -5,8 +5,8 @@ import { createDomainValidation, unknownInputField } from '../../domain/validati
|
|
|
5
5
|
import { inputFieldNodes } from '../../language/value/compile/fields.js';
|
|
6
6
|
import { prepareValueSchema } from '../../language/value/evaluate/preparation.js';
|
|
7
7
|
import { validateValueProgram } from '../../language/value/evaluate/program.js';
|
|
8
|
-
import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
|
|
9
|
-
import {
|
|
8
|
+
import { validateJsonInstance, validatePreparedInstance, } from '../../language/value/evaluate/validate.js';
|
|
9
|
+
import { classifyPreparedOrdering } from '../../language/value/ordering.js';
|
|
10
10
|
import { revisionOfDomainSchema } from '../canonical/revision.js';
|
|
11
11
|
import { SchemaError } from '../diagnostic.js';
|
|
12
12
|
import { validateDomainSchemaSemantics } from '../validation/domain.js';
|
|
@@ -51,15 +51,20 @@ function resolveWithLookup(schema, lookup) {
|
|
|
51
51
|
function sourceValidation(closure, registries) {
|
|
52
52
|
const properties = new Map();
|
|
53
53
|
const callables = new Map();
|
|
54
|
+
const patterns = new Map();
|
|
54
55
|
for (const owner of closure) {
|
|
55
56
|
const registry = registries.get(owner.origin);
|
|
56
57
|
if (registry === undefined)
|
|
57
58
|
throw new TypeError(`No Value registry exists for ${owner.origin}.`);
|
|
58
59
|
for (const definition of Object.values(owner.classes)) {
|
|
59
60
|
for (const [name, property] of Object.entries(definition.properties)) {
|
|
61
|
+
const prepared = prepareValueSchema(property.schema, registry, '', patterns);
|
|
62
|
+
if (!prepared.compiled) {
|
|
63
|
+
throw new TypeError(`Accepted Property cannot be prepared: ${prepared.message}`);
|
|
64
|
+
}
|
|
60
65
|
properties.set(PropertyKey.of(definition.ref, name), Object.freeze({
|
|
61
|
-
ordering:
|
|
62
|
-
validate: (input) =>
|
|
66
|
+
ordering: classifyPreparedOrdering(prepared.value),
|
|
67
|
+
validate: (input) => validatePreparedInstance(prepared.value, input),
|
|
63
68
|
}));
|
|
64
69
|
}
|
|
65
70
|
if (definition.kind !== 'node')
|
|
@@ -69,7 +74,7 @@ function sourceValidation(closure, registries) {
|
|
|
69
74
|
? undefined
|
|
70
75
|
: method.output.mode === 'value'
|
|
71
76
|
? method.output.schema
|
|
72
|
-
: method.output.item, registry));
|
|
77
|
+
: method.output.item, registry, patterns));
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
for (const definition of Object.values(owner.functions)) {
|
|
@@ -77,13 +82,13 @@ function sourceValidation(closure, registries) {
|
|
|
77
82
|
? undefined
|
|
78
83
|
: definition.output.mode === 'value'
|
|
79
84
|
? definition.output.schema
|
|
80
|
-
: definition.output.item, registry));
|
|
85
|
+
: definition.output.item, registry, patterns));
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
return createDomainValidation(properties, callables);
|
|
84
89
|
}
|
|
85
|
-
function callableValidation(input, output, registry) {
|
|
86
|
-
const prepared = prepareValueSchema(input, registry, '');
|
|
90
|
+
function callableValidation(input, output, registry, patterns) {
|
|
91
|
+
const prepared = prepareValueSchema(input, registry, '', patterns);
|
|
87
92
|
if (!prepared.compiled) {
|
|
88
93
|
throw new TypeError(`Accepted callable input cannot be prepared: ${prepared.message}`);
|
|
89
94
|
}
|
|
@@ -91,14 +96,16 @@ function callableValidation(input, output, registry) {
|
|
|
91
96
|
const fields = inputFieldNodes(inputProgram);
|
|
92
97
|
return Object.freeze({
|
|
93
98
|
...(fields === undefined ? {} : { fields: Object.freeze(Object.keys(fields)) }),
|
|
94
|
-
validateInput: (value) =>
|
|
99
|
+
validateInput: (value) => validatePreparedInstance(prepared.value, value),
|
|
95
100
|
validateInputField(field, value) {
|
|
96
101
|
const nodes = fields?.[field];
|
|
97
102
|
return nodes === undefined
|
|
98
103
|
? unknownInputField(field)
|
|
99
104
|
: validateValueProgram(inputProgram, value, nodes);
|
|
100
105
|
},
|
|
101
|
-
validateOutput: (value) => output === undefined
|
|
106
|
+
validateOutput: (value) => output === undefined
|
|
107
|
+
? []
|
|
108
|
+
: validateJsonInstance(output, value, registry, '', patterns),
|
|
102
109
|
});
|
|
103
110
|
}
|
|
104
111
|
function definitionsOf(schema) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DefinitionRef } from '../../addressing/index.js';
|
|
2
2
|
import type { FunctionDefinition, MethodDefinition } from '../../language/callable/model/callable.js';
|
|
3
3
|
import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
4
|
+
import type { CoreEndpointRef } from '../../language/core/model/core.js';
|
|
4
5
|
import type { PolicyDefinition } from '../../language/policy/model/policy.js';
|
|
5
6
|
import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
6
7
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
@@ -15,6 +16,8 @@ export declare function collectUsedForeignOrigins(schema: DomainSchemaIR): Reado
|
|
|
15
16
|
/** Complete structured references in one schema, in canonical order. */
|
|
16
17
|
export declare function collectDomainDefinitionRefs(schema: DomainSchemaIR): readonly DefinitionRef[];
|
|
17
18
|
export declare function collectCoreRefs(schema: DomainSchemaIR, add: ReferenceCollector): void;
|
|
19
|
+
/** The owning Domain addresses no Definition, and a Method is addressed through its owner Class. */
|
|
20
|
+
export declare function coreEndpointDefinitionRef(endpoint: CoreEndpointRef): DefinitionRef | undefined;
|
|
18
21
|
export declare function collectClassRefs(declaration: ClassDefinition, add: ReferenceCollector): void;
|
|
19
22
|
export declare function collectClassPolicyRefs(declaration: ClassDefinition, add: ReferenceCollector): void;
|
|
20
23
|
export declare function collectEndpointRefs(declaration: Extract<ClassDefinition, {
|
|
@@ -36,12 +36,20 @@ export function collectCoreRefs(schema, add) {
|
|
|
36
36
|
add(node.class, 'core');
|
|
37
37
|
for (const edge of schema.core.edges) {
|
|
38
38
|
add(edge.class, 'core');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
for (const endpoint of [edge.source, edge.target]) {
|
|
40
|
+
const ref = coreEndpointDefinitionRef(endpoint);
|
|
41
|
+
// An endpoint names the Definition without instantiating it, exactly like an accepts contract.
|
|
42
|
+
if (ref !== undefined)
|
|
43
|
+
add(ref, 'endpoint');
|
|
44
|
+
}
|
|
43
45
|
}
|
|
44
46
|
}
|
|
47
|
+
/** The owning Domain addresses no Definition, and a Method is addressed through its owner Class. */
|
|
48
|
+
export function coreEndpointDefinitionRef(endpoint) {
|
|
49
|
+
if (endpoint.kind === 'domain')
|
|
50
|
+
return undefined;
|
|
51
|
+
return endpoint.kind === 'method' ? endpoint.owner : endpoint;
|
|
52
|
+
}
|
|
45
53
|
export function collectClassRefs(declaration, add) {
|
|
46
54
|
declaration.extends.forEach((ref) => add(ref, 'extends'));
|
|
47
55
|
Object.values(declaration.properties).forEach((property) => collectValueDefinitionRefs(property.schema, add));
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Key } from '../../addressing/key.js';
|
|
2
2
|
import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
|
+
import { isMethodExecutable } from '../../language/callable/executable.js';
|
|
4
|
+
import { policyObjectKey, policyObjectOrigin, projectedMemberClass, } from '../../language/policy/model/policy.js';
|
|
3
5
|
import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
|
|
4
6
|
import { diagnostic } from '../diagnostic.js';
|
|
5
7
|
const formatDefinitionRef = Key.of;
|
|
@@ -42,8 +44,8 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
|
|
|
42
44
|
const edges = [];
|
|
43
45
|
for (const [index, edge] of environment.candidate.core.edges.entries()) {
|
|
44
46
|
const pointer = `/core/edges/${index}`;
|
|
45
|
-
const source = resolveCoreEndpoint(edge.source, `${pointer}/source`, nodesByRef, diagnostics);
|
|
46
|
-
const target = resolveCoreEndpoint(edge.target, `${pointer}/target`, nodesByRef, diagnostics);
|
|
47
|
+
const source = resolveCoreEndpoint(environment, edge.source, `${pointer}/source`, nodesByRef, diagnostics);
|
|
48
|
+
const target = resolveCoreEndpoint(environment, edge.target, `${pointer}/target`, nodesByRef, diagnostics);
|
|
47
49
|
const resolved = environment.resolve(edge.class);
|
|
48
50
|
if (edge.class.origin !== environment.candidate.origin ||
|
|
49
51
|
resolved === undefined ||
|
|
@@ -75,14 +77,37 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
|
|
|
75
77
|
}
|
|
76
78
|
return edges;
|
|
77
79
|
}
|
|
78
|
-
function resolveCoreEndpoint(ref, pointer, nodesByRef, diagnostics) {
|
|
80
|
+
function resolveCoreEndpoint(environment, ref, pointer, nodesByRef, diagnostics) {
|
|
79
81
|
if (ref.kind === 'domain')
|
|
80
82
|
return { kind: 'domain', ref, key: 'domain' };
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
diagnostics.push(diagnostic('DV_CORE_REF', pointer, `Core endpoint ${
|
|
83
|
+
const undeclared = () => {
|
|
84
|
+
const key = policyObjectKey(ref);
|
|
85
|
+
diagnostics.push(diagnostic('DV_CORE_REF', pointer, `Core endpoint ${key} does not name a declared local Definition.`, key));
|
|
86
|
+
return undefined;
|
|
87
|
+
};
|
|
88
|
+
if (policyObjectOrigin(ref) !== environment.candidate.origin)
|
|
89
|
+
return undeclared();
|
|
90
|
+
if (ref.kind === 'core')
|
|
91
|
+
return nodesByRef.get(definitionRefIndex(ref)) ?? undeclared();
|
|
92
|
+
if (!declaresProjectedMember(environment, ref))
|
|
93
|
+
return undeclared();
|
|
94
|
+
return { kind: 'member', key: policyObjectKey(ref), classRef: projectedMemberClass(ref) };
|
|
95
|
+
}
|
|
96
|
+
/** Only a projected declaration can carry a Core fact; an abstract Method is never projected. */
|
|
97
|
+
function declaresProjectedMember(environment, ref) {
|
|
98
|
+
const candidate = environment.candidate;
|
|
99
|
+
if (ref.kind === 'method') {
|
|
100
|
+
const owner = candidate.classes[ref.owner.name];
|
|
101
|
+
const method = owner?.kind === 'node' ? owner.methods[ref.name] : undefined;
|
|
102
|
+
return method !== undefined && isMethodExecutable(owner, method);
|
|
84
103
|
}
|
|
85
|
-
|
|
104
|
+
if (ref.kind === 'class')
|
|
105
|
+
return Object.hasOwn(candidate.classes, ref.name);
|
|
106
|
+
if (ref.kind === 'function')
|
|
107
|
+
return Object.hasOwn(candidate.functions, ref.name);
|
|
108
|
+
if (ref.kind === 'policy')
|
|
109
|
+
return Object.hasOwn(candidate.policies, ref.name);
|
|
110
|
+
return Object.hasOwn(candidate.views, ref.name);
|
|
86
111
|
}
|
|
87
112
|
function endpointSatisfies(environment, endpoint, accepts) {
|
|
88
113
|
// The owning Domain is a graph-projection identity, not a Definition declared by this DSL.
|
|
@@ -157,41 +182,68 @@ function validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics) {
|
|
|
157
182
|
}
|
|
158
183
|
}
|
|
159
184
|
function validateCardinality(environment, declaration, edges, nodesByRef, diagnostics) {
|
|
185
|
+
const subjects = cardinalitySubjects(edges, nodesByRef);
|
|
160
186
|
if (declaration.orientation === 'undirected') {
|
|
161
187
|
const [first, second] = declaration.endpoints;
|
|
162
|
-
for (const
|
|
163
|
-
const matchesFirst = classSatisfies(environment,
|
|
164
|
-
const matchesSecond = classSatisfies(environment,
|
|
188
|
+
for (const subject of subjects) {
|
|
189
|
+
const matchesFirst = classSatisfies(environment, subject.classRef, first.accepts);
|
|
190
|
+
const matchesSecond = classSatisfies(environment, subject.classRef, second.accepts);
|
|
165
191
|
if (!matchesFirst && !matchesSecond)
|
|
166
192
|
continue;
|
|
167
|
-
const degree = edges.filter((edge) => edge.source.key ===
|
|
193
|
+
const degree = edges.filter((edge) => edge.source.key === subject.key || edge.target.key === subject.key).length;
|
|
168
194
|
if (matchesFirst)
|
|
169
|
-
|
|
195
|
+
checkBound(first, first.incident, degree, subject, diagnostics);
|
|
170
196
|
if (matchesSecond && !sameIncidentEndpointContract(first, second)) {
|
|
171
|
-
|
|
197
|
+
checkBound(second, second.incident, degree, subject, diagnostics);
|
|
172
198
|
}
|
|
173
199
|
}
|
|
174
200
|
return;
|
|
175
201
|
}
|
|
176
|
-
for (const
|
|
177
|
-
if (classSatisfies(environment,
|
|
178
|
-
const degree = edges.filter((edge) => edge.source.key ===
|
|
179
|
-
checkBound(declaration.endpoints.source, declaration.endpoints.source.outgoing, degree,
|
|
202
|
+
for (const subject of subjects) {
|
|
203
|
+
if (classSatisfies(environment, subject.classRef, declaration.endpoints.source.accepts)) {
|
|
204
|
+
const degree = edges.filter((edge) => edge.source.key === subject.key).length;
|
|
205
|
+
checkBound(declaration.endpoints.source, declaration.endpoints.source.outgoing, degree, subject, diagnostics);
|
|
180
206
|
}
|
|
181
|
-
if (classSatisfies(environment,
|
|
182
|
-
const degree = edges.filter((edge) => edge.target.key ===
|
|
183
|
-
checkBound(declaration.endpoints.target, declaration.endpoints.target.incoming, degree,
|
|
207
|
+
if (classSatisfies(environment, subject.classRef, declaration.endpoints.target.accepts)) {
|
|
208
|
+
const degree = edges.filter((edge) => edge.target.key === subject.key).length;
|
|
209
|
+
checkBound(declaration.endpoints.target, declaration.endpoints.target.incoming, degree, subject, diagnostics);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Every declared Core Node proves its own complete degree, so both bounds hold. A Definition
|
|
215
|
+
* endpoint is only named by the Edges that reach it: the Domain may also create ordinary Edges of
|
|
216
|
+
* the same Class against it, so a minimum is unprovable here and only the maximum is enforced.
|
|
217
|
+
*/
|
|
218
|
+
function cardinalitySubjects(edges, nodesByRef) {
|
|
219
|
+
const subjects = new Map();
|
|
220
|
+
for (const node of nodesByRef.values()) {
|
|
221
|
+
subjects.set(node.key, {
|
|
222
|
+
key: node.key,
|
|
223
|
+
classRef: node.classRef,
|
|
224
|
+
pointer: `/core/nodes/${escapePointer(node.slug)}`,
|
|
225
|
+
minimum: true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
for (const edge of edges) {
|
|
229
|
+
for (const endpoint of [edge.source, edge.target]) {
|
|
230
|
+
if (endpoint.kind !== 'member' || subjects.has(endpoint.key))
|
|
231
|
+
continue;
|
|
232
|
+
subjects.set(endpoint.key, {
|
|
233
|
+
key: endpoint.key,
|
|
234
|
+
classRef: endpoint.classRef,
|
|
235
|
+
pointer: edge.pointer,
|
|
236
|
+
minimum: false,
|
|
237
|
+
});
|
|
184
238
|
}
|
|
185
239
|
}
|
|
240
|
+
return Object.freeze([...subjects.values()]);
|
|
186
241
|
}
|
|
187
|
-
function checkBound(endpoint, count, degree,
|
|
242
|
+
function checkBound(endpoint, count, degree, subject, diagnostics) {
|
|
188
243
|
const [minimum, maximum] = countBounds(count);
|
|
189
|
-
if (degree
|
|
244
|
+
if (degree <= maximum && (degree >= minimum || !subject.minimum))
|
|
190
245
|
return;
|
|
191
|
-
diagnostics.push(diagnostic('DV_CORE_EDGE_CONSTRAINTS',
|
|
192
|
-
}
|
|
193
|
-
function checkIncidentBound(endpoint, degree, node, diagnostics) {
|
|
194
|
-
checkBound(endpoint, endpoint.incident, degree, node, diagnostics);
|
|
246
|
+
diagnostics.push(diagnostic('DV_CORE_EDGE_CONSTRAINTS', subject.pointer, `Core degree ${degree} violates ${endpoint.role} Edge count ${count}.`));
|
|
195
247
|
}
|
|
196
248
|
function sameIncidentEndpointContract(left, right) {
|
|
197
249
|
return (left.incident === right.incident &&
|
|
@@ -61,14 +61,19 @@ const coreRefSchema = strictObject({
|
|
|
61
61
|
name: coreSlugSchema,
|
|
62
62
|
});
|
|
63
63
|
const domainRefSchema = strictObject({ kind: literal('domain') });
|
|
64
|
-
|
|
65
|
-
const
|
|
64
|
+
/** Every Definition whose installed projection resolves through one exact member Path. */
|
|
65
|
+
const projectedMemberRefOptions = [
|
|
66
66
|
strictObject({ kind: literal('method'), owner: classRefSchema, name: memberNameSchema }),
|
|
67
67
|
classRefSchema,
|
|
68
68
|
functionRefSchema,
|
|
69
69
|
policyRefSchema,
|
|
70
70
|
viewRefSchema,
|
|
71
71
|
coreRefSchema,
|
|
72
|
+
];
|
|
73
|
+
const policyObjectRefSchema = discriminatedUnion('kind', projectedMemberRefOptions);
|
|
74
|
+
const coreEndpointRefSchema = discriminatedUnion('kind', [
|
|
75
|
+
...projectedMemberRefOptions,
|
|
76
|
+
domainRefSchema,
|
|
72
77
|
]);
|
|
73
78
|
const edgeEndpointBaseShape = {
|
|
74
79
|
role: memberNameSchema,
|
package/dist/value/accept.d.ts
CHANGED
|
@@ -6,3 +6,12 @@ import { type Options } from './limits.js';
|
|
|
6
6
|
* Every source property is obtained from one descriptor snapshot. Accessors are never invoked.
|
|
7
7
|
*/
|
|
8
8
|
export declare function accept(input: unknown, options?: Options): Value;
|
|
9
|
+
/** Admit unknown data into a deeply immutable snapshot; owned subtrees may be shared. */
|
|
10
|
+
export declare function acceptImmutable(input: unknown, options?: Options): Value;
|
|
11
|
+
/** Admit a read-only snapshot, sharing owned immutable subtrees without freezing new wrappers. */
|
|
12
|
+
export declare function acceptForRead(input: unknown, options?: Options): Value;
|
|
13
|
+
/**
|
|
14
|
+
* Private PropertyValues owner seam. Retain immediate recursive values, not the enclosing
|
|
15
|
+
* dictionary: its canonical coordinates are re-admitted by the Property owner on every use.
|
|
16
|
+
*/
|
|
17
|
+
export declare function acceptPropertyRoot(input: unknown): Value;
|