@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.
Files changed (115) hide show
  1. package/dist/auth/authority/admission/admission.d.ts +0 -1
  2. package/dist/auth/authority/admission/admission.js +8 -141
  3. package/dist/auth/authority/admission/references.d.ts +13 -0
  4. package/dist/auth/authority/admission/references.js +38 -0
  5. package/dist/auth/authority/admission/term.d.ts +6 -0
  6. package/dist/auth/authority/admission/term.js +60 -0
  7. package/dist/auth/authority/admission/wire.d.ts +8 -0
  8. package/dist/auth/authority/admission/wire.js +33 -0
  9. package/dist/auth/authority/semantics/interpret.d.ts +6 -0
  10. package/dist/auth/authority/semantics/interpret.js +187 -14
  11. package/dist/auth/authority/semantics/snapshot.d.ts +6 -0
  12. package/dist/graph/graph/index.d.ts +1 -1
  13. package/dist/graph/graph/index.js +1 -1
  14. package/dist/graph/graph/model/codec.js +3 -3
  15. package/dist/graph/graph/model/index.d.ts +1 -1
  16. package/dist/graph/graph/model/index.js +1 -1
  17. package/dist/graph/graph/model/model.d.ts +7 -7
  18. package/dist/graph/graph/model/model.js +6 -6
  19. package/dist/graph/graph/model/wire.d.ts +0 -20
  20. package/dist/graph/graph/model/wire.js +2 -2
  21. package/dist/graph/mutate/authoring/builder.d.ts +2 -2
  22. package/dist/graph/mutate/authoring/builder.js +2 -2
  23. package/dist/graph/mutate/index.d.ts +1 -1
  24. package/dist/graph/mutate/language/codec.js +3 -3
  25. package/dist/graph/mutate/language/model.d.ts +4 -4
  26. package/dist/graph/mutate/language/wire.d.ts +11 -0
  27. package/dist/graph/mutate/result/index.d.ts +4 -3
  28. package/dist/graph/mutate/result/index.js +8 -6
  29. package/dist/graph/mutate/result/wire.d.ts +1 -0
  30. package/dist/graph/mutate/result/wire.js +5 -2
  31. package/dist/graph/node/identity/id.d.ts +0 -3
  32. package/dist/graph/node/identity/id.js +0 -4
  33. package/dist/graph/node/identity/order.js +20 -0
  34. package/dist/graph/query/authoring/builder.js +29 -2
  35. package/dist/graph/query/authoring/model.d.ts +20 -1
  36. package/dist/graph/query/index.d.ts +3 -3
  37. package/dist/graph/query/index.js +1 -1
  38. package/dist/graph/query/language/codec.js +18 -3
  39. package/dist/graph/query/language/fingerprint.js +8 -0
  40. package/dist/graph/query/language/model.d.ts +22 -4
  41. package/dist/graph/query/language/wire.d.ts +11 -0
  42. package/dist/graph/query/language/wire.js +8 -1
  43. package/dist/graph/query/order/index.d.ts +8 -1
  44. package/dist/graph/query/order/index.js +20 -0
  45. package/dist/graph/query/reference/evaluate.js +5 -3
  46. package/dist/graph/reconcile/condition/model.d.ts +2 -2
  47. package/dist/graph/reconcile/condition/references.d.ts +0 -1
  48. package/dist/graph/reconcile/condition/references.js +4 -4
  49. package/dist/graph/reconcile/correspondence.d.ts +9 -8
  50. package/dist/graph/reconcile/evidence.d.ts +2 -2
  51. package/dist/graph/reconcile/mutation/assess.d.ts +2 -1
  52. package/dist/graph/reconcile/observation.d.ts +4 -3
  53. package/dist/graph/reconcile/planning/context.d.ts +3 -3
  54. package/dist/graph/reconcile/planning/model.d.ts +4 -4
  55. package/dist/schema/callables/{view-for.d.ts → entrypoint.d.ts} +7 -5
  56. package/dist/schema/callables/entrypoint.js +12 -0
  57. package/dist/schema/callables/function.d.ts +0 -120
  58. package/dist/schema/callables/index.d.ts +1 -1
  59. package/dist/schema/callables/index.js +1 -1
  60. package/dist/schema/callables/view.d.ts +2 -24
  61. package/dist/schema/domain/domain.d.ts +2 -121
  62. package/dist/schema/domain/domain.js +3 -0
  63. package/dist/schema/identity/identity.d.ts +0 -8
  64. package/dist/schema/kernel/projection/core.d.ts +7 -0
  65. package/dist/schema/kernel/projection/core.js +51 -0
  66. package/dist/schema/kernel/projection/definitions.d.ts +13 -0
  67. package/dist/schema/kernel/projection/definitions.js +71 -0
  68. package/dist/schema/kernel/projection/methods.d.ts +5 -0
  69. package/dist/schema/kernel/projection/methods.js +41 -0
  70. package/dist/schema/kernel/projection/part.d.ts +34 -0
  71. package/dist/schema/kernel/projection/part.js +42 -0
  72. package/dist/schema/kernel/projection/paths.d.ts +6 -0
  73. package/dist/schema/kernel/projection/{keys.js → paths.js} +11 -12
  74. package/dist/schema/kernel/projection/project.d.ts +1 -1
  75. package/dist/schema/kernel/projection/project.js +16 -290
  76. package/dist/schema/kernel/projection/properties.d.ts +11 -0
  77. package/dist/schema/kernel/projection/properties.js +36 -0
  78. package/dist/schema/kernel/projection/relations.d.ts +7 -0
  79. package/dist/schema/kernel/projection/relations.js +49 -0
  80. package/dist/schema/kernel/projection/types.d.ts +4 -4
  81. package/dist/schema/kernel/schema.d.ts +174 -409
  82. package/dist/schema/kernel/schema.js +3 -2
  83. package/dist/schema/kernel/syscalls/contracts/blob.d.ts +12 -2
  84. package/dist/schema/kernel/syscalls/contracts/blob.js +10 -2
  85. package/dist/schema/kernel/syscalls/contracts/delegate.d.ts +0 -4
  86. package/dist/schema/kernel/syscalls/contracts/delegate.js +0 -3
  87. package/dist/schema/kernel/syscalls/contracts/index.d.ts +1 -1
  88. package/dist/schema/kernel/syscalls/contracts/index.js +1 -1
  89. package/dist/schema/kernel/syscalls/contracts/install.d.ts +3 -20
  90. package/dist/schema/kernel/syscalls/contracts/install.js +0 -2
  91. package/dist/schema/kernel/syscalls/contracts/installation.d.ts +0 -36
  92. package/dist/schema/kernel/syscalls/contracts/installation.js +3 -3
  93. package/dist/schema/kernel/syscalls/contracts/mutate.d.ts +13 -0
  94. package/dist/schema/kernel/syscalls/contracts/mutate.js +2 -1
  95. package/dist/schema/kernel/syscalls/contracts/query.d.ts +11 -0
  96. package/dist/schema/kernel/syscalls/contracts/view-resolve.d.ts +3 -14
  97. package/dist/schema/kernel/syscalls/contracts/view-resolve.js +3 -18
  98. package/dist/schema/kernel/syscalls/declarations/delegate.d.ts +0 -4
  99. package/dist/schema/kernel/syscalls/declarations/download.d.ts +5 -0
  100. package/dist/schema/kernel/syscalls/declarations/index.d.ts +30 -24
  101. package/dist/schema/kernel/syscalls/declarations/install.d.ts +0 -8
  102. package/dist/schema/kernel/syscalls/declarations/mutate.d.ts +13 -0
  103. package/dist/schema/kernel/syscalls/declarations/query.d.ts +11 -0
  104. package/dist/schema/kernel/syscalls/declarations/view-resolve.d.ts +1 -12
  105. package/dist/schema/kernel/syscalls/descriptors/identity.d.ts +0 -4
  106. package/dist/schema/kernel/syscalls/descriptors/results/view-resolution.d.ts +2 -13
  107. package/dist/schema/kernel/syscalls/descriptors/results/view-resolution.js +4 -119
  108. package/dist/schema/kernel/syscalls/descriptors/view.d.ts +1 -12
  109. package/package.json +2 -2
  110. package/dist/schema/callables/view-for.js +0 -12
  111. package/dist/schema/kernel/projection/keys.d.ts +0 -3
  112. package/dist/schema/kernel/syscalls/contracts/legacy/delegate.d.ts +0 -5
  113. package/dist/schema/kernel/syscalls/contracts/legacy/delegate.js +0 -25
  114. package/dist/schema/kernel/syscalls/contracts/legacy/install.d.ts +0 -22
  115. 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
- export const MAX_BOOLEAN_OPERANDS = limits.operandsPerExpression;
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) => edgeClassRef(entry, `${path}/via/${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: nodeClassRef(value.class, `${path}/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: edgeClassRef(value.class, `${path}/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) => nodeClassRef(entry, `${path}/classes/${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
- nodes = Object.freeze([...new Set(snapshot.resolve(Path.project(ref)))].filter((node) => snapshot.node(node)));
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 = (source, extent, target, repeat) => {
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 frontier = new Set([source]);
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
- const next = new Set();
88
- for (const node of frontier) {
89
- for (const edge of extent)
90
- for (const reached of step(node, edge))
91
- next.add(reached);
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), expression.via, term(expression.target, frame), expression.repeat);
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
- const bind = (index, locals) => {
159
- if (index === expression.nodes.length)
160
- return holds(expression.where, { ...frame, locals });
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
- return candidates[index].some((node) => bind(index + 1, new Map([...locals, [id, node]])));
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, frame.locals);
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 { LocalBinding, isLocalBinding } from './model/index.js';
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';