@astrale-os/kernel-core 0.9.0-beta.65 → 0.9.0-beta.67
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/auth/authority/admission/admission.d.ts +0 -1
- package/dist/auth/authority/admission/admission.js +8 -141
- package/dist/auth/authority/admission/references.d.ts +13 -0
- package/dist/auth/authority/admission/references.js +38 -0
- package/dist/auth/authority/admission/term.d.ts +6 -0
- package/dist/auth/authority/admission/term.js +60 -0
- package/dist/auth/authority/admission/wire.d.ts +8 -0
- package/dist/auth/authority/admission/wire.js +33 -0
- package/dist/auth/authority/semantics/interpret.d.ts +6 -0
- package/dist/auth/authority/semantics/interpret.js +187 -14
- package/dist/auth/authority/semantics/snapshot.d.ts +6 -0
- package/dist/graph/graph/index.d.ts +1 -1
- package/dist/graph/graph/index.js +1 -1
- package/dist/graph/graph/model/codec.js +3 -3
- package/dist/graph/graph/model/index.d.ts +1 -1
- package/dist/graph/graph/model/index.js +1 -1
- package/dist/graph/graph/model/model.d.ts +7 -7
- package/dist/graph/graph/model/model.js +6 -6
- package/dist/graph/graph/model/wire.d.ts +0 -20
- package/dist/graph/graph/model/wire.js +2 -2
- package/dist/graph/mutate/authoring/builder.d.ts +2 -2
- package/dist/graph/mutate/authoring/builder.js +2 -2
- package/dist/graph/mutate/index.d.ts +1 -1
- package/dist/graph/mutate/language/codec.js +3 -3
- package/dist/graph/mutate/language/model.d.ts +4 -4
- package/dist/graph/mutate/language/wire.d.ts +11 -0
- package/dist/graph/mutate/result/index.d.ts +4 -3
- package/dist/graph/mutate/result/index.js +8 -6
- package/dist/graph/mutate/result/wire.d.ts +1 -0
- package/dist/graph/mutate/result/wire.js +5 -2
- package/dist/graph/node/identity/id.d.ts +0 -3
- package/dist/graph/node/identity/id.js +0 -4
- package/dist/graph/node/identity/order.js +20 -0
- package/dist/graph/query/authoring/builder.js +29 -2
- package/dist/graph/query/authoring/model.d.ts +20 -1
- package/dist/graph/query/index.d.ts +3 -3
- package/dist/graph/query/index.js +1 -1
- package/dist/graph/query/language/codec.js +18 -3
- package/dist/graph/query/language/fingerprint.js +8 -0
- package/dist/graph/query/language/model.d.ts +22 -4
- package/dist/graph/query/language/wire.d.ts +11 -0
- package/dist/graph/query/language/wire.js +8 -1
- package/dist/graph/query/order/index.d.ts +8 -1
- package/dist/graph/query/order/index.js +20 -0
- package/dist/graph/query/reference/evaluate.js +5 -3
- package/dist/graph/reconcile/condition/model.d.ts +2 -2
- package/dist/graph/reconcile/condition/references.d.ts +0 -1
- package/dist/graph/reconcile/condition/references.js +4 -4
- package/dist/graph/reconcile/correspondence.d.ts +9 -8
- package/dist/graph/reconcile/evidence.d.ts +2 -2
- package/dist/graph/reconcile/mutation/assess.d.ts +2 -1
- package/dist/graph/reconcile/observation.d.ts +4 -3
- package/dist/graph/reconcile/planning/context.d.ts +3 -3
- package/dist/graph/reconcile/planning/model.d.ts +4 -4
- package/dist/schema/callables/{view-for.d.ts → entrypoint.d.ts} +7 -5
- package/dist/schema/callables/entrypoint.js +12 -0
- package/dist/schema/callables/function.d.ts +0 -120
- package/dist/schema/callables/index.d.ts +1 -1
- package/dist/schema/callables/index.js +1 -1
- package/dist/schema/callables/view.d.ts +2 -24
- package/dist/schema/domain/domain.d.ts +2 -121
- package/dist/schema/domain/domain.js +3 -0
- package/dist/schema/identity/identity.d.ts +0 -8
- package/dist/schema/kernel/projection/core.d.ts +7 -0
- package/dist/schema/kernel/projection/core.js +51 -0
- package/dist/schema/kernel/projection/definitions.d.ts +13 -0
- package/dist/schema/kernel/projection/definitions.js +71 -0
- package/dist/schema/kernel/projection/methods.d.ts +5 -0
- package/dist/schema/kernel/projection/methods.js +41 -0
- package/dist/schema/kernel/projection/part.d.ts +34 -0
- package/dist/schema/kernel/projection/part.js +42 -0
- package/dist/schema/kernel/projection/paths.d.ts +6 -0
- package/dist/schema/kernel/projection/{keys.js → paths.js} +11 -12
- package/dist/schema/kernel/projection/project.d.ts +1 -1
- package/dist/schema/kernel/projection/project.js +16 -290
- package/dist/schema/kernel/projection/properties.d.ts +11 -0
- package/dist/schema/kernel/projection/properties.js +36 -0
- package/dist/schema/kernel/projection/relations.d.ts +7 -0
- package/dist/schema/kernel/projection/relations.js +49 -0
- package/dist/schema/kernel/projection/types.d.ts +4 -4
- package/dist/schema/kernel/schema.d.ts +174 -409
- package/dist/schema/kernel/schema.js +3 -2
- package/dist/schema/kernel/syscalls/contracts/blob.d.ts +12 -2
- package/dist/schema/kernel/syscalls/contracts/blob.js +10 -2
- package/dist/schema/kernel/syscalls/contracts/delegate.d.ts +0 -4
- package/dist/schema/kernel/syscalls/contracts/delegate.js +0 -3
- package/dist/schema/kernel/syscalls/contracts/index.d.ts +1 -1
- package/dist/schema/kernel/syscalls/contracts/index.js +1 -1
- package/dist/schema/kernel/syscalls/contracts/install.d.ts +3 -20
- package/dist/schema/kernel/syscalls/contracts/install.js +0 -2
- package/dist/schema/kernel/syscalls/contracts/installation.d.ts +0 -36
- package/dist/schema/kernel/syscalls/contracts/installation.js +3 -3
- package/dist/schema/kernel/syscalls/contracts/mutate.d.ts +13 -0
- package/dist/schema/kernel/syscalls/contracts/mutate.js +2 -1
- package/dist/schema/kernel/syscalls/contracts/query.d.ts +11 -0
- package/dist/schema/kernel/syscalls/contracts/view-resolve.d.ts +3 -14
- package/dist/schema/kernel/syscalls/contracts/view-resolve.js +3 -18
- package/dist/schema/kernel/syscalls/declarations/delegate.d.ts +0 -4
- package/dist/schema/kernel/syscalls/declarations/download.d.ts +5 -0
- package/dist/schema/kernel/syscalls/declarations/index.d.ts +30 -24
- package/dist/schema/kernel/syscalls/declarations/install.d.ts +0 -8
- package/dist/schema/kernel/syscalls/declarations/mutate.d.ts +13 -0
- package/dist/schema/kernel/syscalls/declarations/query.d.ts +11 -0
- package/dist/schema/kernel/syscalls/declarations/view-resolve.d.ts +1 -12
- package/dist/schema/kernel/syscalls/descriptors/identity.d.ts +0 -4
- package/dist/schema/kernel/syscalls/descriptors/results/view-resolution.d.ts +2 -13
- package/dist/schema/kernel/syscalls/descriptors/results/view-resolution.js +4 -119
- package/dist/schema/kernel/syscalls/descriptors/view.d.ts +1 -12
- package/package.json +2 -2
- package/dist/schema/callables/view-for.js +0 -12
- package/dist/schema/kernel/projection/keys.d.ts +0 -3
- package/dist/schema/kernel/syscalls/contracts/legacy/delegate.d.ts +0 -5
- package/dist/schema/kernel/syscalls/contracts/legacy/delegate.js +0 -25
- package/dist/schema/kernel/syscalls/contracts/legacy/install.d.ts +0 -22
- package/dist/schema/kernel/syscalls/contracts/legacy/install.js +0 -31
|
@@ -8,7 +8,6 @@ export declare const limits: {
|
|
|
8
8
|
readonly classesPerRelation: 1024;
|
|
9
9
|
readonly repeatMaximum: 16;
|
|
10
10
|
};
|
|
11
|
-
export declare const MAX_BOOLEAN_OPERANDS: 512;
|
|
12
11
|
export declare function admitAuthorityExpr(input: portable.Value): AuthorityExpr;
|
|
13
12
|
/** Internal constructor admission: the outer identity binder binds subject; no local escapes into it. */
|
|
14
13
|
export declare function admitBinderBody(input: portable.Value): AuthorityExpr;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
1
|
import { isNodeId } from '../../../graph/node/index.js';
|
|
3
|
-
import { AuthValueError } from '../../error.js';
|
|
4
2
|
import { ownsExpression } from '../ownership.optimization.js';
|
|
3
|
+
import { classRef } from './references.js';
|
|
4
|
+
import { localId, term } from './term.js';
|
|
5
|
+
import { array, exact, invalid, record, required } from './wire.js';
|
|
5
6
|
export const limits = Object.freeze({
|
|
6
7
|
expressionDepth: 64,
|
|
7
8
|
operandsPerExpression: 512,
|
|
@@ -10,7 +11,7 @@ export const limits = Object.freeze({
|
|
|
10
11
|
classesPerRelation: 1024,
|
|
11
12
|
repeatMaximum: 16,
|
|
12
13
|
});
|
|
13
|
-
|
|
14
|
+
const MAX_BOOLEAN_OPERANDS = limits.operandsPerExpression;
|
|
14
15
|
export function admitAuthorityExpr(input) {
|
|
15
16
|
return admit(input, '/predicate', new Set(), false, 0);
|
|
16
17
|
}
|
|
@@ -67,7 +68,7 @@ function admit(input, path, locals, subjectBound, depth) {
|
|
|
67
68
|
return {
|
|
68
69
|
op: 'related',
|
|
69
70
|
source: term(value.source, `${path}/source`, locals, subjectBound),
|
|
70
|
-
via: array(value.via, `${path}/via`, 1, limits.classesPerRelation).map((entry, index) =>
|
|
71
|
+
via: array(value.via, `${path}/via`, 1, limits.classesPerRelation).map((entry, index) => classRef(entry, `${path}/via/${index}`)),
|
|
71
72
|
target: term(value.target, `${path}/target`, locals, subjectBound),
|
|
72
73
|
...(hasRepeat ? { repeat: repeat(value.repeat, `${path}/repeat`) } : {}),
|
|
73
74
|
};
|
|
@@ -96,11 +97,11 @@ function admit(input, path, locals, subjectBound, depth) {
|
|
|
96
97
|
return {
|
|
97
98
|
op: 'instance-of',
|
|
98
99
|
node: term(value.node, `${path}/node`, locals, subjectBound),
|
|
99
|
-
class:
|
|
100
|
+
class: classRef(value.class, `${path}/class`),
|
|
100
101
|
};
|
|
101
102
|
case 'edge-class':
|
|
102
103
|
exact(value, ['class', 'op'], path);
|
|
103
|
-
return { op: 'edge-class', class:
|
|
104
|
+
return { op: 'edge-class', class: classRef(value.class, `${path}/class`) };
|
|
104
105
|
case 'class-authority':
|
|
105
106
|
exact(value, ['class', 'evidence', 'identity', 'op', 'operation'], path);
|
|
106
107
|
return {
|
|
@@ -114,55 +115,6 @@ function admit(input, path, locals, subjectBound, depth) {
|
|
|
114
115
|
invalid(`${path}/op`, 'Unsupported authority operation');
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
function term(input, path, locals, subjectBound) {
|
|
118
|
-
const value = record(input, path);
|
|
119
|
-
switch (value.kind) {
|
|
120
|
-
case 'input':
|
|
121
|
-
exact(value, ['input', 'kind'], path);
|
|
122
|
-
if (value.input !== 'object' && value.input !== 'source' && value.input !== 'target') {
|
|
123
|
-
invalid(`${path}/input`, 'Expected object, source, or target');
|
|
124
|
-
}
|
|
125
|
-
return { kind: 'input', input: value.input };
|
|
126
|
-
case 'node':
|
|
127
|
-
exact(value, ['id', 'kind'], path);
|
|
128
|
-
if (!isNodeId(value.id))
|
|
129
|
-
invalid(`${path}/id`, 'Expected a canonical NodeId');
|
|
130
|
-
return { kind: 'node', id: value.id };
|
|
131
|
-
case 'class':
|
|
132
|
-
exact(value, ['kind', 'ref'], path);
|
|
133
|
-
return { kind: 'class', ref: classRef(value.ref, `${path}/ref`) };
|
|
134
|
-
case 'domain':
|
|
135
|
-
exact(value, ['kind', 'origin'], path);
|
|
136
|
-
return { kind: 'domain', origin: domainOrigin(value.origin, `${path}/origin`) };
|
|
137
|
-
case 'ref': {
|
|
138
|
-
exact(value, ['kind', 'ref'], path);
|
|
139
|
-
const ref = absoluteRef(value.ref, `${path}/ref`);
|
|
140
|
-
if (ref.kind === 'core')
|
|
141
|
-
return { kind: 'ref', ref: coreRef(value.ref, `${path}/ref`) };
|
|
142
|
-
if (!['class', 'function', 'policy', 'view'].includes(ref.kind))
|
|
143
|
-
invalid(`${path}/ref/kind`, 'Expected a projected Schema member');
|
|
144
|
-
definitionName(ref.name, `${path}/ref/name`);
|
|
145
|
-
return { kind: 'ref', ref: ref };
|
|
146
|
-
}
|
|
147
|
-
case 'core':
|
|
148
|
-
exact(value, ['kind', 'ref'], path);
|
|
149
|
-
return { kind: 'core', ref: coreRef(value.ref, `${path}/ref`) };
|
|
150
|
-
case 'local': {
|
|
151
|
-
exact(value, ['id', 'kind'], path);
|
|
152
|
-
const id = localId(value.id, `${path}/id`);
|
|
153
|
-
if (!locals.has(id))
|
|
154
|
-
invalid(`${path}/id`, `Local ${id} is outside its lexical scope`);
|
|
155
|
-
return { kind: 'local', id };
|
|
156
|
-
}
|
|
157
|
-
case 'subject':
|
|
158
|
-
exact(value, ['kind'], path);
|
|
159
|
-
if (!subjectBound)
|
|
160
|
-
invalid(path, 'Binder subject is outside its lexical scope');
|
|
161
|
-
return { kind: 'subject' };
|
|
162
|
-
default:
|
|
163
|
-
invalid(`${path}/kind`, 'Unsupported authority term');
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
118
|
function localDeclaration(input, path) {
|
|
167
119
|
const value = record(input, path);
|
|
168
120
|
const id = localId(value.id, `${path}/id`);
|
|
@@ -172,7 +124,7 @@ function localDeclaration(input, path) {
|
|
|
172
124
|
}
|
|
173
125
|
if (value.kind === 'classes') {
|
|
174
126
|
exact(value, ['classes', 'id', 'kind'], path);
|
|
175
|
-
const classes = array(value.classes, `${path}/classes`, 1, limits.classesPerLocal).map((entry, index) =>
|
|
127
|
+
const classes = array(value.classes, `${path}/classes`, 1, limits.classesPerLocal).map((entry, index) => classRef(entry, `${path}/classes/${index}`));
|
|
176
128
|
return {
|
|
177
129
|
id,
|
|
178
130
|
kind: 'classes',
|
|
@@ -193,61 +145,6 @@ function repeat(input, path) {
|
|
|
193
145
|
}
|
|
194
146
|
return { min, max };
|
|
195
147
|
}
|
|
196
|
-
function classRef(input, path) {
|
|
197
|
-
const value = absoluteRef(input, path);
|
|
198
|
-
if (value.kind !== 'class')
|
|
199
|
-
invalid(`${path}/kind`, 'Expected class');
|
|
200
|
-
definitionName(value.name, `${path}/name`);
|
|
201
|
-
return value;
|
|
202
|
-
}
|
|
203
|
-
function nodeClassRef(input, path) {
|
|
204
|
-
const value = absoluteRef(input, path);
|
|
205
|
-
if (value.kind !== 'class')
|
|
206
|
-
invalid(`${path}/kind`, 'Expected class');
|
|
207
|
-
definitionName(value.name, `${path}/name`);
|
|
208
|
-
return value;
|
|
209
|
-
}
|
|
210
|
-
function edgeClassRef(input, path) {
|
|
211
|
-
const value = absoluteRef(input, path);
|
|
212
|
-
if (value.kind !== 'class')
|
|
213
|
-
invalid(`${path}/kind`, 'Expected class');
|
|
214
|
-
definitionName(value.name, `${path}/name`);
|
|
215
|
-
return value;
|
|
216
|
-
}
|
|
217
|
-
function absoluteRef(input, path) {
|
|
218
|
-
const value = record(input, path);
|
|
219
|
-
exact(value, ['kind', 'name', 'origin'], path);
|
|
220
|
-
const origin = domainOrigin(value.origin, `${path}/origin`);
|
|
221
|
-
if (typeof value.kind !== 'string')
|
|
222
|
-
invalid(`${path}/kind`, 'Expected a reference kind');
|
|
223
|
-
if (typeof value.name !== 'string')
|
|
224
|
-
invalid(`${path}/name`, 'Expected a reference name');
|
|
225
|
-
return { origin, kind: value.kind, name: value.name };
|
|
226
|
-
}
|
|
227
|
-
function definitionName(input, path) {
|
|
228
|
-
if (!patterns.memberName.test(input))
|
|
229
|
-
invalid(path, 'Expected a Definition member name');
|
|
230
|
-
}
|
|
231
|
-
function coreRef(input, path) {
|
|
232
|
-
const value = absoluteRef(input, path);
|
|
233
|
-
if (value.kind !== 'core')
|
|
234
|
-
invalid(`${path}/kind`, 'Expected core');
|
|
235
|
-
if (!patterns.coreSlug.test(value.name))
|
|
236
|
-
invalid(`${path}/name`, 'Expected a Core slug');
|
|
237
|
-
return value;
|
|
238
|
-
}
|
|
239
|
-
function domainOrigin(input, path) {
|
|
240
|
-
if (typeof input !== 'string' || !patterns.origin.test(input)) {
|
|
241
|
-
invalid(path, 'Expected a canonical Domain origin');
|
|
242
|
-
}
|
|
243
|
-
return input;
|
|
244
|
-
}
|
|
245
|
-
function localId(input, path) {
|
|
246
|
-
if (!Number.isSafeInteger(input) || input < 0) {
|
|
247
|
-
invalid(path, 'Expected a non-negative safe integer');
|
|
248
|
-
}
|
|
249
|
-
return input;
|
|
250
|
-
}
|
|
251
148
|
function operation(input, path) {
|
|
252
149
|
if (input !== 'create' &&
|
|
253
150
|
input !== 'read' &&
|
|
@@ -263,33 +160,3 @@ function evidence(input, path) {
|
|
|
263
160
|
invalid(path, 'Expected allowed or owner');
|
|
264
161
|
return input;
|
|
265
162
|
}
|
|
266
|
-
function record(input, path) {
|
|
267
|
-
if (input === undefined || input === null || Array.isArray(input) || typeof input !== 'object') {
|
|
268
|
-
invalid(path, 'Expected an object');
|
|
269
|
-
}
|
|
270
|
-
return input;
|
|
271
|
-
}
|
|
272
|
-
function array(input, path, minimum, maximum) {
|
|
273
|
-
if (!Array.isArray(input) || input.length < minimum || input.length > maximum) {
|
|
274
|
-
invalid(path, `Expected ${minimum}-${maximum} entries`);
|
|
275
|
-
}
|
|
276
|
-
return input;
|
|
277
|
-
}
|
|
278
|
-
function required(input, path) {
|
|
279
|
-
if (input === undefined)
|
|
280
|
-
invalid(path, 'Expected a value');
|
|
281
|
-
return input;
|
|
282
|
-
}
|
|
283
|
-
function exact(value, expected, path) {
|
|
284
|
-
const actual = Object.keys(value).sort(compare);
|
|
285
|
-
const wanted = [...expected].sort(compare);
|
|
286
|
-
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
|
|
287
|
-
invalid(path, `Expected exactly ${expected.join(', ')}`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
function compare(left, right) {
|
|
291
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
292
|
-
}
|
|
293
|
-
function invalid(path, message) {
|
|
294
|
-
throw new AuthValueError('AUTHORITY_PREDICATE_INVALID', message, path);
|
|
295
|
-
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Key } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import type { ClassRef, CoreRef } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
3
|
+
import type * as portable from '@astrale-os/kernel-dsl/value';
|
|
4
|
+
/** One `{ origin, kind, name }` reference with a canonical origin and an unchecked kind and name. */
|
|
5
|
+
export declare function absoluteRef(input: portable.Value | undefined, path: string): {
|
|
6
|
+
readonly origin: Key.Origin;
|
|
7
|
+
readonly kind: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function classRef(input: portable.Value | undefined, path: string): ClassRef;
|
|
11
|
+
export declare function coreRef(input: portable.Value | undefined, path: string): CoreRef;
|
|
12
|
+
export declare function definitionName(input: string, path: string): void;
|
|
13
|
+
export declare function domainOrigin(input: portable.Value | undefined, path: string): Key.Origin;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
|
+
import { exact, invalid, record } from './wire.js';
|
|
3
|
+
/** One `{ origin, kind, name }` reference with a canonical origin and an unchecked kind and name. */
|
|
4
|
+
export function absoluteRef(input, path) {
|
|
5
|
+
const value = record(input, path);
|
|
6
|
+
exact(value, ['kind', 'name', 'origin'], path);
|
|
7
|
+
const origin = domainOrigin(value.origin, `${path}/origin`);
|
|
8
|
+
if (typeof value.kind !== 'string')
|
|
9
|
+
invalid(`${path}/kind`, 'Expected a reference kind');
|
|
10
|
+
if (typeof value.name !== 'string')
|
|
11
|
+
invalid(`${path}/name`, 'Expected a reference name');
|
|
12
|
+
return { origin, kind: value.kind, name: value.name };
|
|
13
|
+
}
|
|
14
|
+
export function classRef(input, path) {
|
|
15
|
+
const value = absoluteRef(input, path);
|
|
16
|
+
if (value.kind !== 'class')
|
|
17
|
+
invalid(`${path}/kind`, 'Expected class');
|
|
18
|
+
definitionName(value.name, `${path}/name`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function coreRef(input, path) {
|
|
22
|
+
const value = absoluteRef(input, path);
|
|
23
|
+
if (value.kind !== 'core')
|
|
24
|
+
invalid(`${path}/kind`, 'Expected core');
|
|
25
|
+
if (!patterns.coreSlug.test(value.name))
|
|
26
|
+
invalid(`${path}/name`, 'Expected a Core slug');
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
export function definitionName(input, path) {
|
|
30
|
+
if (!patterns.memberName.test(input))
|
|
31
|
+
invalid(path, 'Expected a Definition member name');
|
|
32
|
+
}
|
|
33
|
+
export function domainOrigin(input, path) {
|
|
34
|
+
if (typeof input !== 'string' || !patterns.origin.test(input)) {
|
|
35
|
+
invalid(path, 'Expected a canonical Domain origin');
|
|
36
|
+
}
|
|
37
|
+
return input;
|
|
38
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type * as portable from '@astrale-os/kernel-dsl/value';
|
|
2
|
+
import type { Term } from '../language/index.js';
|
|
3
|
+
/** Admit one term; `locals` and `subjectBound` are the lexical scope it may reference. */
|
|
4
|
+
export declare function term(input: portable.Value | undefined, path: string, locals: ReadonlySet<number>, subjectBound: boolean): Term;
|
|
5
|
+
/** A local id or repeat bound: a non-negative safe integer. */
|
|
6
|
+
export declare function localId(input: portable.Value | undefined, path: string): number;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isNodeId } from '../../../graph/node/index.js';
|
|
2
|
+
import { absoluteRef, classRef, coreRef, definitionName, domainOrigin } from './references.js';
|
|
3
|
+
import { exact, invalid, record } from './wire.js';
|
|
4
|
+
/** Admit one term; `locals` and `subjectBound` are the lexical scope it may reference. */
|
|
5
|
+
export function term(input, path, locals, subjectBound) {
|
|
6
|
+
const value = record(input, path);
|
|
7
|
+
switch (value.kind) {
|
|
8
|
+
case 'input':
|
|
9
|
+
exact(value, ['input', 'kind'], path);
|
|
10
|
+
if (value.input !== 'object' && value.input !== 'source' && value.input !== 'target') {
|
|
11
|
+
invalid(`${path}/input`, 'Expected object, source, or target');
|
|
12
|
+
}
|
|
13
|
+
return { kind: 'input', input: value.input };
|
|
14
|
+
case 'node':
|
|
15
|
+
exact(value, ['id', 'kind'], path);
|
|
16
|
+
if (!isNodeId(value.id))
|
|
17
|
+
invalid(`${path}/id`, 'Expected a canonical NodeId');
|
|
18
|
+
return { kind: 'node', id: value.id };
|
|
19
|
+
case 'class':
|
|
20
|
+
exact(value, ['kind', 'ref'], path);
|
|
21
|
+
return { kind: 'class', ref: classRef(value.ref, `${path}/ref`) };
|
|
22
|
+
case 'domain':
|
|
23
|
+
exact(value, ['kind', 'origin'], path);
|
|
24
|
+
return { kind: 'domain', origin: domainOrigin(value.origin, `${path}/origin`) };
|
|
25
|
+
case 'ref': {
|
|
26
|
+
exact(value, ['kind', 'ref'], path);
|
|
27
|
+
const ref = absoluteRef(value.ref, `${path}/ref`);
|
|
28
|
+
if (ref.kind === 'core')
|
|
29
|
+
return { kind: 'ref', ref: coreRef(value.ref, `${path}/ref`) };
|
|
30
|
+
if (!['class', 'function', 'policy', 'view'].includes(ref.kind))
|
|
31
|
+
invalid(`${path}/ref/kind`, 'Expected a projected Schema member');
|
|
32
|
+
definitionName(ref.name, `${path}/ref/name`);
|
|
33
|
+
return { kind: 'ref', ref: ref };
|
|
34
|
+
}
|
|
35
|
+
case 'core':
|
|
36
|
+
exact(value, ['kind', 'ref'], path);
|
|
37
|
+
return { kind: 'core', ref: coreRef(value.ref, `${path}/ref`) };
|
|
38
|
+
case 'local': {
|
|
39
|
+
exact(value, ['id', 'kind'], path);
|
|
40
|
+
const id = localId(value.id, `${path}/id`);
|
|
41
|
+
if (!locals.has(id))
|
|
42
|
+
invalid(`${path}/id`, `Local ${id} is outside its lexical scope`);
|
|
43
|
+
return { kind: 'local', id };
|
|
44
|
+
}
|
|
45
|
+
case 'subject':
|
|
46
|
+
exact(value, ['kind'], path);
|
|
47
|
+
if (!subjectBound)
|
|
48
|
+
invalid(path, 'Binder subject is outside its lexical scope');
|
|
49
|
+
return { kind: 'subject' };
|
|
50
|
+
default:
|
|
51
|
+
invalid(`${path}/kind`, 'Unsupported authority term');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** A local id or repeat bound: a non-negative safe integer. */
|
|
55
|
+
export function localId(input, path) {
|
|
56
|
+
if (!Number.isSafeInteger(input) || input < 0) {
|
|
57
|
+
invalid(path, 'Expected a non-negative safe integer');
|
|
58
|
+
}
|
|
59
|
+
return input;
|
|
60
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as portable from '@astrale-os/kernel-dsl/value';
|
|
2
|
+
/** Fail admission at one JSON-pointer path with the stable Authority predicate code. */
|
|
3
|
+
export declare function invalid(path: string, message: string): never;
|
|
4
|
+
export declare function record(input: portable.Value | undefined, path: string): portable.Object;
|
|
5
|
+
export declare function array(input: portable.Value | undefined, path: string, minimum: number, maximum: number): readonly portable.Value[];
|
|
6
|
+
export declare function required(input: portable.Value | undefined, path: string): portable.Value;
|
|
7
|
+
/** Reject a record whose own keys differ from `expected`, in any order. */
|
|
8
|
+
export declare function exact(value: portable.Object, expected: readonly string[], path: string): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AuthValueError } from '../../error.js';
|
|
2
|
+
/** Fail admission at one JSON-pointer path with the stable Authority predicate code. */
|
|
3
|
+
export function invalid(path, message) {
|
|
4
|
+
throw new AuthValueError('AUTHORITY_PREDICATE_INVALID', message, path);
|
|
5
|
+
}
|
|
6
|
+
export function record(input, path) {
|
|
7
|
+
if (input === undefined || input === null || Array.isArray(input) || typeof input !== 'object') {
|
|
8
|
+
invalid(path, 'Expected an object');
|
|
9
|
+
}
|
|
10
|
+
return input;
|
|
11
|
+
}
|
|
12
|
+
export function array(input, path, minimum, maximum) {
|
|
13
|
+
if (!Array.isArray(input) || input.length < minimum || input.length > maximum) {
|
|
14
|
+
invalid(path, `Expected ${minimum}-${maximum} entries`);
|
|
15
|
+
}
|
|
16
|
+
return input;
|
|
17
|
+
}
|
|
18
|
+
export function required(input, path) {
|
|
19
|
+
if (input === undefined)
|
|
20
|
+
invalid(path, 'Expected a value');
|
|
21
|
+
return input;
|
|
22
|
+
}
|
|
23
|
+
/** Reject a record whose own keys differ from `expected`, in any order. */
|
|
24
|
+
export function exact(value, expected, path) {
|
|
25
|
+
const actual = Object.keys(value).sort(compare);
|
|
26
|
+
const wanted = [...expected].sort(compare);
|
|
27
|
+
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
|
|
28
|
+
invalid(path, `Expected exactly ${expected.join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function compare(left, right) {
|
|
32
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
33
|
+
}
|
|
@@ -15,6 +15,12 @@ export interface AuthorityEvaluationOptions {
|
|
|
15
15
|
* as a captured fact table. Undefined leaves the atom to its meaning over the snapshot.
|
|
16
16
|
*/
|
|
17
17
|
readonly atom?: (atom: AuthorityAtom, subject: NodeId | undefined) => boolean | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Pure observation of the snapshot's evidence token. It changes (Object.is) at every
|
|
20
|
+
* acquisition and stays stable otherwise; observing it must not acquire evidence.
|
|
21
|
+
* Stable snapshots may omit it. Tokens must not be mutated or reused across acquisitions.
|
|
22
|
+
*/
|
|
23
|
+
readonly evidenceEpoch?: () => unknown;
|
|
18
24
|
}
|
|
19
25
|
/** Decisions of one program over one snapshot. Memoized evidence never outlives the evaluator. */
|
|
20
26
|
export interface AuthorityEvaluator {
|
|
@@ -10,6 +10,9 @@ export function createAuthorityEvaluator(program, snapshot, vocabulary, options
|
|
|
10
10
|
const references = new Map();
|
|
11
11
|
const closures = new Map();
|
|
12
12
|
const projections = new Map();
|
|
13
|
+
const walks = new Map();
|
|
14
|
+
const walkPlans = new WeakMap();
|
|
15
|
+
const existentialPlans = new WeakMap();
|
|
13
16
|
const instanceOf = classKey(vocabulary.instanceOf);
|
|
14
17
|
const reference = (path) => {
|
|
15
18
|
const known = references.get(path.raw);
|
|
@@ -65,7 +68,8 @@ export function createAuthorityEvaluator(program, snapshot, vocabulary, options
|
|
|
65
68
|
const key = classKey(ref);
|
|
66
69
|
let nodes = projections.get(key);
|
|
67
70
|
if (nodes === undefined) {
|
|
68
|
-
|
|
71
|
+
const candidates = snapshot.classProjection?.(ref) ?? snapshot.resolve(Path.project(ref));
|
|
72
|
+
nodes = Object.freeze([...new Set(candidates)].filter((node) => snapshot.node(node)));
|
|
69
73
|
projections.set(key, nodes);
|
|
70
74
|
}
|
|
71
75
|
return nodes;
|
|
@@ -77,19 +81,52 @@ export function createAuthorityEvaluator(program, snapshot, vocabulary, options
|
|
|
77
81
|
* Bounded walks: a Node reached at any depth within [min, max], revisits allowed. Each hop may
|
|
78
82
|
* take any member of the Edge Class extent.
|
|
79
83
|
*/
|
|
80
|
-
const related = (
|
|
84
|
+
const related = (expression, source, target) => {
|
|
85
|
+
const { repeat, via: extent } = expression;
|
|
81
86
|
const min = repeat?.min ?? 1;
|
|
82
87
|
const max = repeat?.max ?? 1;
|
|
88
|
+
// The empty walk is already a proof. Do not spend a read budget exploring longer walks.
|
|
83
89
|
if (min === 0 && source === target)
|
|
84
90
|
return true;
|
|
85
|
-
let
|
|
91
|
+
let plan = walkPlans.get(expression);
|
|
92
|
+
if (plan === undefined) {
|
|
93
|
+
const key = JSON.stringify([min, max, extent.map(classKey)]);
|
|
94
|
+
plan = walks.get(key);
|
|
95
|
+
if (plan === undefined) {
|
|
96
|
+
plan = new Map();
|
|
97
|
+
walks.set(key, plan);
|
|
98
|
+
}
|
|
99
|
+
walkPlans.set(expression, plan);
|
|
100
|
+
}
|
|
101
|
+
const epoch = options.evidenceEpoch?.();
|
|
102
|
+
let walk = plan.get(source);
|
|
103
|
+
if (walk === undefined || !Object.is(walk.epoch, epoch)) {
|
|
104
|
+
walk = { epoch, layers: [new Set([source])] };
|
|
105
|
+
plan.delete(source);
|
|
106
|
+
}
|
|
107
|
+
let frontier = walk.layers[0];
|
|
86
108
|
for (let depth = 1; depth <= max && frontier.size > 0; depth++) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
109
|
+
let next = walk.layers[depth];
|
|
110
|
+
if (next === undefined) {
|
|
111
|
+
const before = options.evidenceEpoch?.();
|
|
112
|
+
const acquired = new Set();
|
|
113
|
+
for (const node of frontier) {
|
|
114
|
+
for (const edge of extent)
|
|
115
|
+
for (const reached of step(node, edge))
|
|
116
|
+
acquired.add(reached);
|
|
117
|
+
}
|
|
118
|
+
next = acquired;
|
|
119
|
+
walk.layers.push(next);
|
|
120
|
+
// Every retained layer must belong to the same epoch. A provisional prefix remains
|
|
121
|
+
// usable for this search only, even if its later extensions acquire no further evidence.
|
|
122
|
+
if (Object.is(walk.epoch, before) && Object.is(before, options.evidenceEpoch?.())) {
|
|
123
|
+
plan.set(source, walk);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
plan.delete(source);
|
|
127
|
+
}
|
|
92
128
|
}
|
|
129
|
+
// Like the frozen interpreter, acquire no layer beyond the first admissible witness.
|
|
93
130
|
if (depth >= min && next.has(target))
|
|
94
131
|
return true;
|
|
95
132
|
frontier = next;
|
|
@@ -127,7 +164,7 @@ export function createAuthorityEvaluator(program, snapshot, vocabulary, options
|
|
|
127
164
|
case 'same-node':
|
|
128
165
|
return term(expression.left, frame) === term(expression.right, frame);
|
|
129
166
|
case 'related':
|
|
130
|
-
return related(term(expression.source, frame),
|
|
167
|
+
return related(expression, term(expression.source, frame), term(expression.target, frame));
|
|
131
168
|
case 'instance-of':
|
|
132
169
|
return sameClass(classOf(term(expression.node, frame)), expression.class);
|
|
133
170
|
case 'edge-class': {
|
|
@@ -155,13 +192,111 @@ export function createAuthorityEvaluator(program, snapshot, vocabulary, options
|
|
|
155
192
|
};
|
|
156
193
|
const exists = (expression, frame) => {
|
|
157
194
|
const candidates = expression.nodes.map((local) => local.kind === 'any' ? snapshot.nodes() : snapshot.nodes(local.classes));
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
195
|
+
// Acquire every extent in declaration order, even when the product has no tuple.
|
|
196
|
+
if (candidates.some((extent) => extent.length === 0))
|
|
197
|
+
return false;
|
|
198
|
+
let plan = existentialPlans.get(expression);
|
|
199
|
+
if (plan === undefined) {
|
|
200
|
+
const predicates = expression.where.op === 'all' ? expression.where.predicates : [expression.where];
|
|
201
|
+
const usedIndices = new Set();
|
|
202
|
+
// Each entry describes the whole prefix, including its earlier true predicates.
|
|
203
|
+
// gates[i] rejects values of local i; resets[i] invalidates their earlier context.
|
|
204
|
+
const gates = expression.nodes.map(() => []);
|
|
205
|
+
const resets = expression.nodes.map(() => []);
|
|
206
|
+
const dependency = predicates.map((predicate, item) => {
|
|
207
|
+
const used = localDependencies(predicate);
|
|
208
|
+
for (let index = 0; index < expression.nodes.length; index++) {
|
|
209
|
+
if (used.has(expression.nodes[index].id))
|
|
210
|
+
usedIndices.add(index);
|
|
211
|
+
}
|
|
212
|
+
const ordered = [...usedIndices].sort((left, right) => left - right);
|
|
213
|
+
const last = ordered.at(-1) ?? -1;
|
|
214
|
+
const earlier = ordered.at(-2) ?? -1;
|
|
215
|
+
if (last >= 0)
|
|
216
|
+
gates[last].push(item);
|
|
217
|
+
// An exclusion of the last dependency is valid only while all earlier ones stay fixed.
|
|
218
|
+
for (let index = 0; index <= earlier; index++)
|
|
219
|
+
resets[index].push(item);
|
|
220
|
+
return last;
|
|
221
|
+
});
|
|
222
|
+
plan = { predicates, dependency, gates, resets };
|
|
223
|
+
existentialPlans.set(expression, plan);
|
|
224
|
+
}
|
|
225
|
+
const { predicates, dependency, gates, resets } = plan;
|
|
226
|
+
const excluded = predicates.map(() => new Set());
|
|
227
|
+
const rejected = (index, node) => {
|
|
228
|
+
for (const item of gates[index])
|
|
229
|
+
if (excluded[item].has(node))
|
|
230
|
+
return true;
|
|
231
|
+
return false;
|
|
232
|
+
};
|
|
233
|
+
const clear = (item) => {
|
|
234
|
+
if (excluded[item].size === 0)
|
|
235
|
+
return;
|
|
236
|
+
excluded[item].clear();
|
|
237
|
+
};
|
|
238
|
+
let prefix = 0;
|
|
239
|
+
let observedEpoch = options.evidenceEpoch?.();
|
|
240
|
+
const refresh = () => {
|
|
241
|
+
const current = options.evidenceEpoch?.();
|
|
242
|
+
if (Object.is(current, observedEpoch))
|
|
243
|
+
return false;
|
|
244
|
+
observedEpoch = current;
|
|
245
|
+
prefix = 0;
|
|
246
|
+
excluded.forEach((_, item) => clear(item));
|
|
247
|
+
return true;
|
|
248
|
+
};
|
|
249
|
+
// -2 means a witness. Every other result identifies the last bound variable that
|
|
250
|
+
// may change the evaluated prefix. Descendants cannot alter its reads or result.
|
|
251
|
+
const locals = new Map(frame.locals);
|
|
252
|
+
const complete = { ...frame, locals };
|
|
253
|
+
const bind = (index) => {
|
|
254
|
+
if (index === expression.nodes.length) {
|
|
255
|
+
for (let item = prefix; item < predicates.length; item++) {
|
|
256
|
+
if (!holds(predicates[item], complete)) {
|
|
257
|
+
if (refresh())
|
|
258
|
+
return expression.nodes.length - 1;
|
|
259
|
+
const target = dependency[item];
|
|
260
|
+
if (target >= 0) {
|
|
261
|
+
const value = locals.get(expression.nodes[target].id);
|
|
262
|
+
if (!excluded[item].has(value)) {
|
|
263
|
+
excluded[item].add(value);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return dependency[item];
|
|
267
|
+
}
|
|
268
|
+
prefix = item + 1;
|
|
269
|
+
}
|
|
270
|
+
return -2;
|
|
271
|
+
}
|
|
161
272
|
const id = expression.nodes[index].id;
|
|
162
|
-
|
|
273
|
+
const previous = locals.get(id);
|
|
274
|
+
try {
|
|
275
|
+
for (let ordinal = 0; ordinal < candidates[index].length; ordinal++) {
|
|
276
|
+
refresh();
|
|
277
|
+
const node = candidates[index][ordinal];
|
|
278
|
+
if (rejected(index, node)) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
for (const item of resets[index])
|
|
282
|
+
clear(item);
|
|
283
|
+
while (prefix > 0 && dependency[prefix - 1] >= index)
|
|
284
|
+
prefix--;
|
|
285
|
+
locals.set(id, node);
|
|
286
|
+
const result = bind(index + 1);
|
|
287
|
+
if (result === -2 || result < index)
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
return index - 1;
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
if (previous === undefined)
|
|
294
|
+
locals.delete(id);
|
|
295
|
+
else
|
|
296
|
+
locals.set(id, previous);
|
|
297
|
+
}
|
|
163
298
|
};
|
|
164
|
-
return bind(0
|
|
299
|
+
return bind(0) === -2;
|
|
165
300
|
};
|
|
166
301
|
return Object.freeze({
|
|
167
302
|
admit() {
|
|
@@ -184,6 +319,44 @@ export function createAuthorityEvaluator(program, snapshot, vocabulary, options
|
|
|
184
319
|
},
|
|
185
320
|
});
|
|
186
321
|
}
|
|
322
|
+
/** Locals whose assignment can affect the result of this expression. */
|
|
323
|
+
function localDependencies(expression) {
|
|
324
|
+
const result = new Set();
|
|
325
|
+
const addTerm = (term) => {
|
|
326
|
+
if (term.kind === 'local')
|
|
327
|
+
result.add(term.id);
|
|
328
|
+
};
|
|
329
|
+
const visit = (value) => {
|
|
330
|
+
switch (value.op) {
|
|
331
|
+
case 'all':
|
|
332
|
+
case 'any':
|
|
333
|
+
value.predicates.forEach(visit);
|
|
334
|
+
return;
|
|
335
|
+
case 'not':
|
|
336
|
+
visit(value.predicate);
|
|
337
|
+
return;
|
|
338
|
+
case 'as':
|
|
339
|
+
case 'exists':
|
|
340
|
+
visit(value.where);
|
|
341
|
+
return;
|
|
342
|
+
case 'same-node':
|
|
343
|
+
addTerm(value.left);
|
|
344
|
+
addTerm(value.right);
|
|
345
|
+
return;
|
|
346
|
+
case 'related':
|
|
347
|
+
addTerm(value.source);
|
|
348
|
+
addTerm(value.target);
|
|
349
|
+
return;
|
|
350
|
+
case 'instance-of':
|
|
351
|
+
addTerm(value.node);
|
|
352
|
+
return;
|
|
353
|
+
case 'class-authority':
|
|
354
|
+
addTerm(value.identity);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
visit(expression);
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
187
360
|
/** Every Identity a binder of the program names, sorted. */
|
|
188
361
|
export function binderRoots(expressions) {
|
|
189
362
|
const roots = new Set();
|
|
@@ -21,6 +21,12 @@ export interface AuthoritySnapshot {
|
|
|
21
21
|
orientation(edge: ClassRef): 'directed' | 'undirected';
|
|
22
22
|
/** Every live Node the Path reaches; a fixed term requires exactly one. */
|
|
23
23
|
resolve(path: Path): readonly NodeId[];
|
|
24
|
+
/**
|
|
25
|
+
* Pinned Registry candidates for derived `instance_of`, admitted through `node` before use.
|
|
26
|
+
* An absent hook falls back to `resolve(Path.project(ref))`; an empty result never does.
|
|
27
|
+
* Fixed terms always use `resolve` independently of this projection.
|
|
28
|
+
*/
|
|
29
|
+
classProjection?(ref: ClassRef): readonly NodeId[];
|
|
24
30
|
/** Every live Node, restricted to exact Classes when given. */
|
|
25
31
|
nodes(classes?: readonly ClassRef[]): readonly NodeId[];
|
|
26
32
|
}
|
|
@@ -4,7 +4,7 @@ export type { Delta, DeltaConflict, DeltaResult } from './delta/index.js';
|
|
|
4
4
|
export type { MergeConflict, MergeResult } from './merge/index.js';
|
|
5
5
|
export { GraphData, GraphDataDecodeError, GraphSpec, GraphSpecDecodeError } from './model/index.js';
|
|
6
6
|
export type { AnonymousNodeSpec, GraphDataJson, GraphSpecEdge, GraphSpecEndpoint, GraphSpecJson, GraphSpecNode, } from './model/index.js';
|
|
7
|
-
export {
|
|
7
|
+
export { LocalAlias, isLocalAlias } from './model/index.js';
|
|
8
8
|
export type { GraphAdmissionContext, GraphClassInfo, GraphDiagnostic } from './admission/index.js';
|
|
9
9
|
export { GraphAdmissionError } from './admission/index.js';
|
|
10
10
|
export type { GraphEdges, GraphNodes } from './indexes/index.js';
|