@astrale-os/kernel-core 0.9.0-beta.62 → 0.9.0-beta.64
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 +2 -2
- package/dist/auth/authority/admission/admission.js +10 -8
- package/dist/auth/authority/admission/index.d.ts +1 -1
- package/dist/auth/authority/admission/index.js +1 -1
- package/dist/auth/authority/index.d.ts +7 -3
- package/dist/auth/authority/index.js +4 -2
- package/dist/auth/authority/language/index.d.ts +2 -0
- package/dist/auth/authority/language/index.js +1 -0
- package/dist/auth/authority/language/predicate.d.ts +10 -5
- package/dist/auth/authority/language/predicate.js +1 -1
- package/dist/auth/authority/language/program.d.ts +21 -0
- package/dist/auth/authority/language/program.js +2 -0
- package/dist/auth/authority/normalization/index.d.ts +1 -1
- package/dist/auth/authority/normalization/index.js +1 -1
- package/dist/auth/authority/normalization/normalize.d.ts +1 -1
- package/dist/auth/authority/normalization/normalize.js +4 -5
- package/dist/auth/authority/ownership.optimization.js +2 -2
- package/dist/auth/authority/prepare-binder-body.d.ts +4 -0
- package/dist/auth/authority/prepare-binder-body.js +31 -0
- package/dist/auth/authority/program.d.ts +28 -0
- package/dist/auth/authority/program.js +191 -0
- package/dist/auth/authority/semantics/class-authority.d.ts +8 -0
- package/dist/auth/authority/semantics/class-authority.js +26 -0
- package/dist/auth/authority/semantics/composition.d.ts +39 -0
- package/dist/auth/authority/semantics/composition.js +112 -0
- package/dist/auth/authority/semantics/failure.d.ts +21 -0
- package/dist/auth/authority/semantics/failure.js +10 -0
- package/dist/auth/authority/semantics/index.d.ts +11 -0
- package/dist/auth/authority/semantics/index.js +5 -0
- package/dist/auth/authority/semantics/interpret.d.ts +35 -0
- package/dist/auth/authority/semantics/interpret.js +215 -0
- package/dist/auth/authority/semantics/snapshot.d.ts +34 -0
- package/dist/auth/authority/semantics/terms.d.ts +20 -0
- package/dist/auth/authority/semantics/terms.js +96 -0
- package/dist/auth/authority/semantics/vocabulary.d.ts +19 -0
- package/dist/auth/authority/semantics/vocabulary.js +1 -0
- package/dist/auth/context/context.d.ts +4 -3
- package/dist/auth/expression/authoring/builder.d.ts +2 -11
- package/dist/auth/expression/authoring/builder.js +1 -45
- package/dist/auth/expression/authoring/index.d.ts +1 -1
- package/dist/auth/expression/authoring/index.js +1 -1
- package/dist/auth/expression/index.d.ts +1 -2
- package/dist/auth/expression/index.js +1 -2
- package/dist/auth/expression/language/identity-expression.d.ts +2 -22
- package/dist/auth/grant/index.d.ts +1 -1
- package/dist/auth/grant/index.js +1 -1
- package/dist/auth/grant/resolved/admission.d.ts +4 -3
- package/dist/auth/grant/resolved/admission.js +23 -13
- package/dist/auth/grant/resolved/grant.d.ts +1 -1
- package/dist/auth/grant/resolved/index.d.ts +1 -1
- package/dist/auth/grant/resolved/index.js +1 -1
- package/dist/auth/grant/unresolved/admission.d.ts +4 -1
- package/dist/auth/grant/unresolved/admission.js +27 -67
- package/dist/auth/grant/unresolved/limits.d.ts +2 -4
- package/dist/auth/grant/unresolved/limits.js +1 -5
- package/dist/auth/index.d.ts +1 -1
- package/dist/graph/graph/graph.d.ts +1 -5
- package/dist/graph/graph/graph.js +3 -27
- package/dist/graph/graph/index.d.ts +1 -3
- package/dist/graph/graph/index.js +1 -2
- package/dist/graph/query/index.d.ts +2 -0
- package/dist/graph/query/index.js +1 -0
- package/dist/graph/query/order/index.d.ts +46 -0
- package/dist/graph/query/order/index.js +59 -0
- package/dist/graph/query/reference/evaluate.js +24 -50
- package/dist/graph/reconcile/planning/canonical.d.ts +6 -0
- package/dist/graph/reconcile/planning/canonical.js +28 -0
- package/dist/graph/reconcile/planning/model.d.ts +4 -0
- package/dist/graph/reconcile/planning/plan.js +2 -1
- package/dist/schema/authority/vocabulary.d.ts +3 -0
- package/dist/schema/authority/vocabulary.js +25 -0
- package/dist/schema/identity/composition.d.ts +5 -0
- package/dist/schema/identity/composition.js +5 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/introspection/language.d.ts +2 -0
- package/dist/schema/kernel/schema.d.ts +125 -79
- package/dist/schema/kernel/syscalls/contracts/install.d.ts +86 -7
- package/dist/schema/kernel/syscalls/contracts/install.js +6 -3
- package/dist/schema/kernel/syscalls/contracts/installation.d.ts +30 -0
- package/dist/schema/kernel/syscalls/contracts/installation.js +9 -0
- package/dist/schema/kernel/syscalls/contracts/introspect.d.ts +5 -2
- package/dist/schema/kernel/syscalls/contracts/introspect.js +9 -5
- package/dist/schema/kernel/syscalls/contracts/legacy/install.d.ts +22 -0
- package/dist/schema/kernel/syscalls/contracts/legacy/install.js +31 -0
- package/dist/schema/kernel/syscalls/contracts/uninstall.d.ts +10 -0
- package/dist/schema/kernel/syscalls/declarations/index.d.ts +49 -25
- package/dist/schema/kernel/syscalls/declarations/install.d.ts +17 -2
- package/dist/schema/kernel/syscalls/declarations/introspect.d.ts +2 -0
- package/dist/schema/kernel/syscalls/declarations/mutate.d.ts +23 -23
- package/dist/schema/kernel/syscalls/declarations/uninstall.d.ts +7 -0
- package/dist/schema/kernel/syscalls/descriptors/results/view-resolution.d.ts +7 -0
- package/package.json +2 -2
- package/dist/auth/authority/prepare-grant-body.d.ts +0 -4
- package/dist/auth/authority/prepare-grant-body.js +0 -27
- package/dist/auth/expression/normalization/canonicalize.d.ts +0 -7
- package/dist/auth/expression/normalization/canonicalize.js +0 -102
- package/dist/auth/expression/normalization/index.d.ts +0 -2
- package/dist/auth/expression/normalization/index.js +0 -2
- package/dist/auth/expression/normalization/raise-denials.d.ts +0 -3
- package/dist/auth/expression/normalization/raise-denials.js +0 -59
- package/dist/graph/graph/navigation/error.d.ts +0 -12
- package/dist/graph/graph/navigation/error.js +0 -20
- package/dist/graph/graph/navigation/index.d.ts +0 -2
- package/dist/graph/graph/navigation/index.js +0 -1
- package/dist/graph/graph/navigation/model.d.ts +0 -15
- package/dist/schema/kernel/projection/path.d.ts +0 -2
- package/dist/schema/kernel/projection/path.js +0 -113
- /package/dist/{graph/graph/navigation/model.js → auth/authority/semantics/snapshot.js} +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { AuthorityFailure } from './failure.js';
|
|
2
|
+
/** A binder admits its Identity when its composed status is positive and not denied. */
|
|
3
|
+
export function allowed(status) {
|
|
4
|
+
return status.positive && !status.denied;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Close the composition of one binder root: the outgoing `extends_with`, `constrained_by` and
|
|
8
|
+
* `excluded_from` Edges, followed transitively. The root must be live and every member must have a
|
|
9
|
+
* concrete Identity Class of `identities`; otherwise the composition is invalid, not false.
|
|
10
|
+
*/
|
|
11
|
+
export function closeComposition(root, snapshot, vocabulary, identities, limits) {
|
|
12
|
+
const allowedClasses = new Set(identities.map(classKey));
|
|
13
|
+
const { extension, constraint, exclusion } = vocabulary.composition;
|
|
14
|
+
const members = [];
|
|
15
|
+
const ordinals = new Map();
|
|
16
|
+
const children = [];
|
|
17
|
+
let edges = 0;
|
|
18
|
+
const admit = (id) => {
|
|
19
|
+
let ordinal = ordinals.get(id);
|
|
20
|
+
if (ordinal !== undefined)
|
|
21
|
+
return ordinal;
|
|
22
|
+
const node = snapshot.node(id);
|
|
23
|
+
if (node === undefined) {
|
|
24
|
+
throw new AuthorityFailure('AUTHORITY_COMPOSITION_INVALID', 'An Identity binder names no live Node.', { identity: id });
|
|
25
|
+
}
|
|
26
|
+
if (!allowedClasses.has(classKey(node.class))) {
|
|
27
|
+
throw new AuthorityFailure('AUTHORITY_COMPOSITION_INVALID', 'An Identity composition reaches a Node that is not an Identity.', { identity: id });
|
|
28
|
+
}
|
|
29
|
+
ordinal = members.length;
|
|
30
|
+
if (limits !== undefined && ordinal >= limits.compositionNodes) {
|
|
31
|
+
throw new AuthorityFailure('AUTHORITY_BUDGET_EXCEEDED', 'An Identity composition exceeds its Node ceiling.', { root, limit: limits.compositionNodes });
|
|
32
|
+
}
|
|
33
|
+
members.push(id);
|
|
34
|
+
ordinals.set(id, ordinal);
|
|
35
|
+
children.push({ extensions: [], constraints: [], exclusions: [] });
|
|
36
|
+
return ordinal;
|
|
37
|
+
};
|
|
38
|
+
admit(root);
|
|
39
|
+
for (let next = 0; next < members.length; next++) {
|
|
40
|
+
const id = members[next];
|
|
41
|
+
const row = children[next];
|
|
42
|
+
const follow = (edge, bucket) => {
|
|
43
|
+
for (const child of snapshot.edges(id, edge, 'out')) {
|
|
44
|
+
edges += 1;
|
|
45
|
+
if (limits !== undefined && edges > limits.compositionEdges) {
|
|
46
|
+
throw new AuthorityFailure('AUTHORITY_BUDGET_EXCEEDED', 'An Identity composition exceeds its Edge ceiling.', { root, limit: limits.compositionEdges });
|
|
47
|
+
}
|
|
48
|
+
bucket.push(admit(child));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
follow(extension, row.extensions);
|
|
52
|
+
follow(constraint, row.constraints);
|
|
53
|
+
follow(exclusion, row.exclusions);
|
|
54
|
+
}
|
|
55
|
+
const order = childrenFirst(children);
|
|
56
|
+
const acyclic = order.length === members.length;
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
root,
|
|
59
|
+
identities: Object.freeze(members),
|
|
60
|
+
acyclic,
|
|
61
|
+
status(leaf) {
|
|
62
|
+
if (!acyclic)
|
|
63
|
+
return undefined;
|
|
64
|
+
const values = [];
|
|
65
|
+
// Iterative in the dependency order, so a deep valid composition needs no call stack.
|
|
66
|
+
for (const ordinal of order) {
|
|
67
|
+
const row = children[ordinal];
|
|
68
|
+
const truth = leaf(members[ordinal]);
|
|
69
|
+
const full = (child) => values[child].full;
|
|
70
|
+
const positive = (child) => values[child].positive;
|
|
71
|
+
const denied = (child) => values[child].denied;
|
|
72
|
+
values[ordinal] = Object.freeze({
|
|
73
|
+
full: (truth || row.extensions.some(full)) &&
|
|
74
|
+
row.constraints.every(full) &&
|
|
75
|
+
!row.exclusions.some(full),
|
|
76
|
+
positive: (truth || row.extensions.some(positive)) && row.constraints.every(positive),
|
|
77
|
+
denied: row.exclusions.some(full) ||
|
|
78
|
+
row.extensions.some(denied) ||
|
|
79
|
+
row.constraints.some(denied),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return values[0];
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Kahn order over the closure; shorter than the closure exactly when it contains a cycle. */
|
|
87
|
+
function childrenFirst(children) {
|
|
88
|
+
const parents = children.map(() => []);
|
|
89
|
+
const pending = new Uint32Array(children.length);
|
|
90
|
+
children.forEach((row, ordinal) => {
|
|
91
|
+
const dependencies = new Set([...row.extensions, ...row.constraints, ...row.exclusions]);
|
|
92
|
+
pending[ordinal] = dependencies.size;
|
|
93
|
+
for (const child of dependencies)
|
|
94
|
+
parents[child].push(ordinal);
|
|
95
|
+
});
|
|
96
|
+
const order = [];
|
|
97
|
+
pending.forEach((count, ordinal) => {
|
|
98
|
+
if (count === 0)
|
|
99
|
+
order.push(ordinal);
|
|
100
|
+
});
|
|
101
|
+
for (let next = 0; next < order.length; next++) {
|
|
102
|
+
for (const parent of parents[order[next]]) {
|
|
103
|
+
pending[parent] = pending[parent] - 1;
|
|
104
|
+
if (pending[parent] === 0)
|
|
105
|
+
order.push(parent);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return order;
|
|
109
|
+
}
|
|
110
|
+
function classKey(ref) {
|
|
111
|
+
return `${ref.origin}:${ref.kind}.${ref.name}`;
|
|
112
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An authority decision that cannot be made. A failure is never `false`: under `not` it would
|
|
3
|
+
* become `true`, so every backend surfaces it as a failed request instead of a decision.
|
|
4
|
+
*/
|
|
5
|
+
export type AuthorityFailureCode =
|
|
6
|
+
/** A bound input names no live Node. */
|
|
7
|
+
'AUTHORITY_TARGET_NOT_FOUND'
|
|
8
|
+
/** A term reads an input the decision frame does not bind. */
|
|
9
|
+
| 'AUTHORITY_INPUT_UNBOUND'
|
|
10
|
+
/** A fixed term does not resolve to exactly one live Node. */
|
|
11
|
+
| 'AUTHORITY_REFERENCE_INVALID'
|
|
12
|
+
/** A binder root is absent, or its composition reaches a Node that is not an Identity. */
|
|
13
|
+
| 'AUTHORITY_COMPOSITION_INVALID'
|
|
14
|
+
/** A binder composition exceeds the evaluating backend's ceilings. */
|
|
15
|
+
| 'AUTHORITY_BUDGET_EXCEEDED';
|
|
16
|
+
export declare class AuthorityFailure extends Error {
|
|
17
|
+
readonly code: AuthorityFailureCode;
|
|
18
|
+
readonly details: Readonly<Record<string, unknown>>;
|
|
19
|
+
readonly name: 'AuthorityFailure';
|
|
20
|
+
constructor(code: AuthorityFailureCode, message: string, details?: Readonly<Record<string, unknown>>);
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { classAuthorityMeaning } from './class-authority.js';
|
|
2
|
+
export { allowed, closeComposition } from './composition.js';
|
|
3
|
+
export type { CompositionClosure, CompositionLimits, CompositionStatus } from './composition.js';
|
|
4
|
+
export { AuthorityFailure } from './failure.js';
|
|
5
|
+
export type { AuthorityFailureCode } from './failure.js';
|
|
6
|
+
export { binderRoots, createAuthorityEvaluator } from './interpret.js';
|
|
7
|
+
export type { AuthorityAtom, AuthorityEvaluationOptions, AuthorityEvaluator } from './interpret.js';
|
|
8
|
+
export type { AuthorityInputs, AuthorityNode, AuthoritySnapshot } from './snapshot.js';
|
|
9
|
+
export { fixedPaths, isFixedTerm, requiredInputs, termPath } from './terms.js';
|
|
10
|
+
export type { FixedTerm } from './terms.js';
|
|
11
|
+
export type { AuthorityVocabulary } from './vocabulary.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { classAuthorityMeaning } from './class-authority.js';
|
|
2
|
+
export { allowed, closeComposition } from './composition.js';
|
|
3
|
+
export { AuthorityFailure } from './failure.js';
|
|
4
|
+
export { binderRoots, createAuthorityEvaluator } from './interpret.js';
|
|
5
|
+
export { fixedPaths, isFixedTerm, requiredInputs, termPath } from './terms.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { NodeId } from '../../../graph/node/index.js';
|
|
2
|
+
import type { AuthorityExpr, AuthorityProgram, PredicateRef } from '../language/index.js';
|
|
3
|
+
import type { CompositionLimits } from './composition.js';
|
|
4
|
+
import type { AuthorityInputs, AuthoritySnapshot } from './snapshot.js';
|
|
5
|
+
import type { AuthorityVocabulary } from './vocabulary.js';
|
|
6
|
+
/** A leaf the Boolean and binder structure does not decompose. */
|
|
7
|
+
export type AuthorityAtom = Exclude<AuthorityExpr, {
|
|
8
|
+
readonly op: 'true' | 'false' | 'all' | 'any' | 'not' | 'as';
|
|
9
|
+
}>;
|
|
10
|
+
export interface AuthorityEvaluationOptions {
|
|
11
|
+
/** The evaluating backend's composition ceilings. */
|
|
12
|
+
readonly limits?: CompositionLimits;
|
|
13
|
+
/**
|
|
14
|
+
* Decide an atom outside every existential scope from evidence the snapshot does not hold, such
|
|
15
|
+
* as a captured fact table. Undefined leaves the atom to its meaning over the snapshot.
|
|
16
|
+
*/
|
|
17
|
+
readonly atom?: (atom: AuthorityAtom, subject: NodeId | undefined) => boolean | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Decisions of one program over one snapshot. Memoized evidence never outlives the evaluator. */
|
|
20
|
+
export interface AuthorityEvaluator {
|
|
21
|
+
/**
|
|
22
|
+
* Resolve every fixed reference and admit every binder composition of the program, as a backend
|
|
23
|
+
* does for its whole request family before it exposes any decision.
|
|
24
|
+
*/
|
|
25
|
+
admit(): void;
|
|
26
|
+
/** Decide one predicate for one input frame; throws `AuthorityFailure`, never returns it. */
|
|
27
|
+
evaluate(predicate: PredicateRef, inputs?: AuthorityInputs): boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The reference interpreter of authority. It is parametric over one snapshot and the Kernel
|
|
31
|
+
* vocabulary, and every backend lowering must agree with it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createAuthorityEvaluator(program: AuthorityProgram, snapshot: AuthoritySnapshot, vocabulary: AuthorityVocabulary, options?: AuthorityEvaluationOptions): AuthorityEvaluator;
|
|
34
|
+
/** Every Identity a binder of the program names, sorted. */
|
|
35
|
+
export declare function binderRoots(expressions: readonly AuthorityExpr[]): readonly NodeId[];
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { Path } from '../../../graph/path/index.js';
|
|
2
|
+
import { allowed, closeComposition } from './composition.js';
|
|
3
|
+
import { AuthorityFailure } from './failure.js';
|
|
4
|
+
import { fixedPaths, termPath } from './terms.js';
|
|
5
|
+
/**
|
|
6
|
+
* The reference interpreter of authority. It is parametric over one snapshot and the Kernel
|
|
7
|
+
* vocabulary, and every backend lowering must agree with it.
|
|
8
|
+
*/
|
|
9
|
+
export function createAuthorityEvaluator(program, snapshot, vocabulary, options = {}) {
|
|
10
|
+
const references = new Map();
|
|
11
|
+
const closures = new Map();
|
|
12
|
+
const projections = new Map();
|
|
13
|
+
const instanceOf = classKey(vocabulary.instanceOf);
|
|
14
|
+
const reference = (path) => {
|
|
15
|
+
const known = references.get(path.raw);
|
|
16
|
+
if (known !== undefined)
|
|
17
|
+
return known;
|
|
18
|
+
const nodes = [...new Set(snapshot.resolve(path))].filter((id) => snapshot.node(id));
|
|
19
|
+
if (nodes.length !== 1) {
|
|
20
|
+
throw new AuthorityFailure('AUTHORITY_REFERENCE_INVALID', 'An authority fixed reference does not resolve to exactly one live Node.', { path: path.raw, candidates: nodes.length });
|
|
21
|
+
}
|
|
22
|
+
references.set(path.raw, nodes[0]);
|
|
23
|
+
return nodes[0];
|
|
24
|
+
};
|
|
25
|
+
const closure = (root) => {
|
|
26
|
+
let value = closures.get(root);
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
value = closeComposition(root, snapshot, vocabulary, program.identities, options.limits);
|
|
29
|
+
closures.set(root, value);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
const term = (value, frame) => {
|
|
34
|
+
switch (value.kind) {
|
|
35
|
+
case 'input': {
|
|
36
|
+
const id = frame.inputs[value.input];
|
|
37
|
+
if (id === undefined) {
|
|
38
|
+
throw new AuthorityFailure('AUTHORITY_INPUT_UNBOUND', `Authority input ${value.input} is not bound by the decision frame.`);
|
|
39
|
+
}
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
case 'subject':
|
|
43
|
+
if (frame.subject === undefined)
|
|
44
|
+
throw new TypeError('Authority subject is unbound.');
|
|
45
|
+
return frame.subject;
|
|
46
|
+
case 'local': {
|
|
47
|
+
const id = frame.locals.get(value.id);
|
|
48
|
+
if (id === undefined)
|
|
49
|
+
throw new TypeError(`Authority local ${value.id} is unbound.`);
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
52
|
+
default:
|
|
53
|
+
return reference(termPath(value));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const classOf = (id) => {
|
|
57
|
+
const node = snapshot.node(id);
|
|
58
|
+
if (node === undefined)
|
|
59
|
+
throw new TypeError('Authority evaluation reached a Node that is gone.');
|
|
60
|
+
return node.class;
|
|
61
|
+
};
|
|
62
|
+
/** `instance_of` is derived: the live projection of the Node's exact Class. */
|
|
63
|
+
const projection = (id) => {
|
|
64
|
+
const ref = classOf(id);
|
|
65
|
+
const key = classKey(ref);
|
|
66
|
+
let nodes = projections.get(key);
|
|
67
|
+
if (nodes === undefined) {
|
|
68
|
+
nodes = Object.freeze([...new Set(snapshot.resolve(Path.project(ref)))].filter((node) => snapshot.node(node)));
|
|
69
|
+
projections.set(key, nodes);
|
|
70
|
+
}
|
|
71
|
+
return nodes;
|
|
72
|
+
};
|
|
73
|
+
const step = (from, edge) => classKey(edge) === instanceOf
|
|
74
|
+
? projection(from)
|
|
75
|
+
: snapshot.edges(from, edge, snapshot.orientation(edge) === 'undirected' ? 'both' : 'out');
|
|
76
|
+
/**
|
|
77
|
+
* Bounded walks: a Node reached at any depth within [min, max], revisits allowed. Each hop may
|
|
78
|
+
* take any member of the Edge Class extent.
|
|
79
|
+
*/
|
|
80
|
+
const related = (source, extent, target, repeat) => {
|
|
81
|
+
const min = repeat?.min ?? 1;
|
|
82
|
+
const max = repeat?.max ?? 1;
|
|
83
|
+
if (min === 0 && source === target)
|
|
84
|
+
return true;
|
|
85
|
+
let frontier = new Set([source]);
|
|
86
|
+
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);
|
|
92
|
+
}
|
|
93
|
+
if (depth >= min && next.has(target))
|
|
94
|
+
return true;
|
|
95
|
+
frontier = next;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
const holds = (expression, frame) => {
|
|
100
|
+
switch (expression.op) {
|
|
101
|
+
case 'true':
|
|
102
|
+
return true;
|
|
103
|
+
case 'false':
|
|
104
|
+
return false;
|
|
105
|
+
case 'all':
|
|
106
|
+
return expression.predicates.every((child) => holds(child, frame));
|
|
107
|
+
case 'any':
|
|
108
|
+
return expression.predicates.some((child) => holds(child, frame));
|
|
109
|
+
case 'not':
|
|
110
|
+
return !holds(expression.predicate, frame);
|
|
111
|
+
case 'as': {
|
|
112
|
+
// A cyclic composition makes this binder false; the scope of a cycle is the binder.
|
|
113
|
+
const status = closure(expression.identity).status((identity) => holds(expression.where, { ...frame, subject: identity }));
|
|
114
|
+
return status !== undefined && allowed(status);
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
return atom(expression, frame);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const atom = (expression, frame) => {
|
|
121
|
+
if (options.atom !== undefined && frame.locals.size === 0) {
|
|
122
|
+
const decided = options.atom(expression, frame.subject);
|
|
123
|
+
if (decided !== undefined)
|
|
124
|
+
return decided;
|
|
125
|
+
}
|
|
126
|
+
switch (expression.op) {
|
|
127
|
+
case 'same-node':
|
|
128
|
+
return term(expression.left, frame) === term(expression.right, frame);
|
|
129
|
+
case 'related':
|
|
130
|
+
return related(term(expression.source, frame), expression.via, term(expression.target, frame), expression.repeat);
|
|
131
|
+
case 'instance-of':
|
|
132
|
+
return sameClass(classOf(term(expression.node, frame)), expression.class);
|
|
133
|
+
case 'edge-class': {
|
|
134
|
+
if (frame.inputs.edge === undefined) {
|
|
135
|
+
throw new AuthorityFailure('AUTHORITY_INPUT_UNBOUND', 'Authority edge-class requires a bound Edge object.');
|
|
136
|
+
}
|
|
137
|
+
return sameClass(frame.inputs.edge, expression.class);
|
|
138
|
+
}
|
|
139
|
+
case 'class-authority': {
|
|
140
|
+
// The meaning is `classAuthorityMeaning`; it is decided directly so no atom it expands to
|
|
141
|
+
// is ever asked of a captured fact table.
|
|
142
|
+
const identity = term(expression.identity, frame);
|
|
143
|
+
if (identity === reference(Path.domain(expression.class.origin)))
|
|
144
|
+
return true;
|
|
145
|
+
if (expression.evidence === 'owner')
|
|
146
|
+
return false;
|
|
147
|
+
const target = reference(Path.project(expression.class));
|
|
148
|
+
return snapshot
|
|
149
|
+
.edges(identity, vocabulary.capabilities[expression.operation], 'out')
|
|
150
|
+
.includes(target);
|
|
151
|
+
}
|
|
152
|
+
case 'exists':
|
|
153
|
+
return exists(expression, frame);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const exists = (expression, frame) => {
|
|
157
|
+
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 });
|
|
161
|
+
const id = expression.nodes[index].id;
|
|
162
|
+
return candidates[index].some((node) => bind(index + 1, new Map([...locals, [id, node]])));
|
|
163
|
+
};
|
|
164
|
+
return bind(0, frame.locals);
|
|
165
|
+
};
|
|
166
|
+
return Object.freeze({
|
|
167
|
+
admit() {
|
|
168
|
+
for (const path of fixedPaths(program.predicates))
|
|
169
|
+
reference(path);
|
|
170
|
+
for (const root of binderRoots(program.predicates))
|
|
171
|
+
closure(root);
|
|
172
|
+
},
|
|
173
|
+
evaluate(predicate, inputs = {}) {
|
|
174
|
+
const expression = program.predicates[predicate];
|
|
175
|
+
if (expression === undefined)
|
|
176
|
+
throw new TypeError('Unknown Authority predicate ordinal.');
|
|
177
|
+
for (const input of ['object', 'source', 'target']) {
|
|
178
|
+
const id = inputs[input];
|
|
179
|
+
if (id !== undefined && snapshot.node(id) === undefined) {
|
|
180
|
+
throw new AuthorityFailure('AUTHORITY_TARGET_NOT_FOUND', `Authority ${input} does not exist in the evaluated graph.`, { [input]: id });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return holds(expression, { inputs, subject: undefined, locals: new Map() });
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** Every Identity a binder of the program names, sorted. */
|
|
188
|
+
export function binderRoots(expressions) {
|
|
189
|
+
const roots = new Set();
|
|
190
|
+
const visit = (expression) => {
|
|
191
|
+
switch (expression.op) {
|
|
192
|
+
case 'all':
|
|
193
|
+
case 'any':
|
|
194
|
+
expression.predicates.forEach(visit);
|
|
195
|
+
return;
|
|
196
|
+
case 'not':
|
|
197
|
+
visit(expression.predicate);
|
|
198
|
+
return;
|
|
199
|
+
case 'exists':
|
|
200
|
+
visit(expression.where);
|
|
201
|
+
return;
|
|
202
|
+
case 'as':
|
|
203
|
+
roots.add(expression.identity);
|
|
204
|
+
visit(expression.where);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
expressions.forEach(visit);
|
|
208
|
+
return Object.freeze([...roots].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)));
|
|
209
|
+
}
|
|
210
|
+
function sameClass(left, right) {
|
|
211
|
+
return left.origin === right.origin && left.kind === right.kind && left.name === right.name;
|
|
212
|
+
}
|
|
213
|
+
function classKey(ref) {
|
|
214
|
+
return `${ref.origin}:${ref.kind}.${ref.name}`;
|
|
215
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { NodeId } from '../../../graph/node/index.js';
|
|
2
|
+
import type { Path } from '../../../graph/path/index.js';
|
|
3
|
+
import type { ClassRef } from '../language/index.js';
|
|
4
|
+
/** One live Node as authority sees it: its exact concrete Class. */
|
|
5
|
+
export interface AuthorityNode {
|
|
6
|
+
readonly class: ClassRef;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* One consistent graph state the interpreter reads. Every Node it exposes is live and has exactly
|
|
10
|
+
* one active concrete Class; every Edge connects two such Nodes.
|
|
11
|
+
*/
|
|
12
|
+
export interface AuthoritySnapshot {
|
|
13
|
+
/** The live Node, or undefined when the id names none. */
|
|
14
|
+
node(id: NodeId): AuthorityNode | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Distinct Nodes one Edge of the exact Class away: `out` follows source to target, `in` target
|
|
17
|
+
* to source, `both` either orientation. The derived `instance_of` relation is never asked.
|
|
18
|
+
*/
|
|
19
|
+
edges(from: NodeId, edge: ClassRef, direction: 'out' | 'in' | 'both'): readonly NodeId[];
|
|
20
|
+
/** Orientation the pinned Registry declares for one Edge Class. */
|
|
21
|
+
orientation(edge: ClassRef): 'directed' | 'undirected';
|
|
22
|
+
/** Every live Node the Path reaches; a fixed term requires exactly one. */
|
|
23
|
+
resolve(path: Path): readonly NodeId[];
|
|
24
|
+
/** Every live Node, restricted to exact Classes when given. */
|
|
25
|
+
nodes(classes?: readonly ClassRef[]): readonly NodeId[];
|
|
26
|
+
}
|
|
27
|
+
/** The input frame of one decision; each bound Node must be live. */
|
|
28
|
+
export interface AuthorityInputs {
|
|
29
|
+
readonly object?: NodeId;
|
|
30
|
+
readonly source?: NodeId;
|
|
31
|
+
readonly target?: NodeId;
|
|
32
|
+
/** Exact Class of the bound Edge object, read only by `edge-class`. */
|
|
33
|
+
readonly edge?: ClassRef;
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AuthorityExpr, Term } from '../language/index.js';
|
|
2
|
+
import type { AuthorityInputs } from './snapshot.js';
|
|
3
|
+
import { Path } from '../../../graph/path/index.js';
|
|
4
|
+
/** A term that names one Node independently of any row, binder or local. */
|
|
5
|
+
export type FixedTerm = Extract<Term, {
|
|
6
|
+
readonly kind: 'node' | 'class' | 'domain' | 'core' | 'ref';
|
|
7
|
+
}>;
|
|
8
|
+
export declare function isFixedTerm(term: Term): term is FixedTerm;
|
|
9
|
+
/** The Path a fixed term denotes; the term holds when that Path reaches exactly one live Node. */
|
|
10
|
+
export declare function termPath(term: FixedTerm): Path;
|
|
11
|
+
/**
|
|
12
|
+
* Every fixed Path one expression depends on, sorted by raw text. `class-authority` depends on the
|
|
13
|
+
* projection of its Class and on the Domain of the Class origin.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fixedPaths(expressions: readonly AuthorityExpr[]): readonly Path[];
|
|
16
|
+
/**
|
|
17
|
+
* Every decision input the expressions read: each `input` term, and the bound Edge Class that
|
|
18
|
+
* `edge-class` reads. A decision whose frame leaves one of them unbound fails.
|
|
19
|
+
*/
|
|
20
|
+
export declare function requiredInputs(expressions: readonly AuthorityExpr[]): ReadonlySet<keyof AuthorityInputs>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Path } from '../../../graph/path/index.js';
|
|
2
|
+
export function isFixedTerm(term) {
|
|
3
|
+
return term.kind !== 'input' && term.kind !== 'local' && term.kind !== 'subject';
|
|
4
|
+
}
|
|
5
|
+
/** The Path a fixed term denotes; the term holds when that Path reaches exactly one live Node. */
|
|
6
|
+
export function termPath(term) {
|
|
7
|
+
switch (term.kind) {
|
|
8
|
+
case 'node':
|
|
9
|
+
return Path.id(term.id);
|
|
10
|
+
case 'class':
|
|
11
|
+
return Path.project(term.ref);
|
|
12
|
+
case 'domain':
|
|
13
|
+
return Path.domain(term.origin);
|
|
14
|
+
case 'ref':
|
|
15
|
+
case 'core':
|
|
16
|
+
return Path.project(term.ref);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Every fixed Path one expression depends on, sorted by raw text. `class-authority` depends on the
|
|
21
|
+
* projection of its Class and on the Domain of the Class origin.
|
|
22
|
+
*/
|
|
23
|
+
export function fixedPaths(expressions) {
|
|
24
|
+
const paths = new Map();
|
|
25
|
+
const add = (path) => {
|
|
26
|
+
paths.set(path.raw, path);
|
|
27
|
+
};
|
|
28
|
+
for (const expression of subexpressions(expressions)) {
|
|
29
|
+
for (const term of ownTerms(expression))
|
|
30
|
+
if (isFixedTerm(term))
|
|
31
|
+
add(termPath(term));
|
|
32
|
+
if (expression.op === 'class-authority') {
|
|
33
|
+
add(Path.project(expression.class));
|
|
34
|
+
add(Path.domain(expression.class.origin));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze([...paths.values()].sort((left, right) => compare(left.raw, right.raw)));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Every decision input the expressions read: each `input` term, and the bound Edge Class that
|
|
41
|
+
* `edge-class` reads. A decision whose frame leaves one of them unbound fails.
|
|
42
|
+
*/
|
|
43
|
+
export function requiredInputs(expressions) {
|
|
44
|
+
const inputs = new Set();
|
|
45
|
+
for (const expression of subexpressions(expressions)) {
|
|
46
|
+
if (expression.op === 'edge-class')
|
|
47
|
+
inputs.add('edge');
|
|
48
|
+
for (const term of ownTerms(expression))
|
|
49
|
+
if (term.kind === 'input')
|
|
50
|
+
inputs.add(term.input);
|
|
51
|
+
}
|
|
52
|
+
return inputs;
|
|
53
|
+
}
|
|
54
|
+
/** Every expression and nested expression, depth first in syntax order. */
|
|
55
|
+
function* subexpressions(expressions) {
|
|
56
|
+
for (const expression of expressions) {
|
|
57
|
+
yield expression;
|
|
58
|
+
switch (expression.op) {
|
|
59
|
+
case 'all':
|
|
60
|
+
case 'any':
|
|
61
|
+
yield* subexpressions(expression.predicates);
|
|
62
|
+
break;
|
|
63
|
+
case 'not':
|
|
64
|
+
yield* subexpressions([expression.predicate]);
|
|
65
|
+
break;
|
|
66
|
+
case 'as':
|
|
67
|
+
case 'exists':
|
|
68
|
+
yield* subexpressions([expression.where]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** The terms one expression reads itself, excluding those of its nested expressions. */
|
|
73
|
+
function ownTerms(expression) {
|
|
74
|
+
switch (expression.op) {
|
|
75
|
+
case 'same-node':
|
|
76
|
+
return [expression.left, expression.right];
|
|
77
|
+
case 'related':
|
|
78
|
+
return [expression.source, expression.target];
|
|
79
|
+
case 'instance-of':
|
|
80
|
+
return [expression.node];
|
|
81
|
+
case 'class-authority':
|
|
82
|
+
return [expression.identity];
|
|
83
|
+
case 'true':
|
|
84
|
+
case 'false':
|
|
85
|
+
case 'edge-class':
|
|
86
|
+
case 'all':
|
|
87
|
+
case 'any':
|
|
88
|
+
case 'not':
|
|
89
|
+
case 'as':
|
|
90
|
+
case 'exists':
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function compare(left, right) {
|
|
95
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
96
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClassRef, Operation } from '../language/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The Kernel Classes that give authority its meaning. Core Schema publishes the Kernel instance;
|
|
4
|
+
* the interpreter and every lowering take it as a parameter instead of discovering it.
|
|
5
|
+
*/
|
|
6
|
+
export interface AuthorityVocabulary {
|
|
7
|
+
/** Node Class whose concrete descendants an Identity binder may reach. */
|
|
8
|
+
readonly identity: ClassRef;
|
|
9
|
+
/** Outgoing composition Edges of one Identity. */
|
|
10
|
+
readonly composition: {
|
|
11
|
+
readonly extension: ClassRef;
|
|
12
|
+
readonly constraint: ClassRef;
|
|
13
|
+
readonly exclusion: ClassRef;
|
|
14
|
+
};
|
|
15
|
+
/** Edge from an Identity to a Class projection that delegates one Class operation. */
|
|
16
|
+
readonly capabilities: Readonly<Record<Operation, ClassRef>>;
|
|
17
|
+
/** The derived relation from a Node to the projection of its exact Class. */
|
|
18
|
+
readonly instanceOf: ClassRef;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,7 +2,6 @@ import type { Attestation } from '../attestation/index.js';
|
|
|
2
2
|
import type { CredentialInput } from '../credential/input/index.js';
|
|
3
3
|
import type { VerifiedCredential } from '../credential/verified/index.js';
|
|
4
4
|
import type { Delegation } from '../delegation/index.js';
|
|
5
|
-
import type { Grant } from '../grant/index.js';
|
|
6
5
|
import type { IdentityId } from '../identity/index.js';
|
|
7
6
|
/** The original credential together with the claims established by verification. */
|
|
8
7
|
export interface CredentialContext {
|
|
@@ -12,15 +11,17 @@ export interface CredentialContext {
|
|
|
12
11
|
/** Verified credential result before every runtime authority value is necessarily resolved. */
|
|
13
12
|
export interface Authenticated {
|
|
14
13
|
readonly credential: CredentialContext;
|
|
15
|
-
readonly
|
|
14
|
+
readonly caller?: IdentityId;
|
|
16
15
|
readonly attestation?: Attestation;
|
|
17
16
|
readonly delegation?: Delegation;
|
|
18
17
|
}
|
|
19
18
|
/** Complete authenticated kernel context. */
|
|
20
19
|
export interface AuthContext {
|
|
21
20
|
readonly credential: CredentialContext;
|
|
21
|
+
/** Authenticated executor: ceiling, installed ownership, Root bypass, and audit. */
|
|
22
22
|
readonly principal: IdentityId;
|
|
23
|
-
|
|
23
|
+
/** The one Identity whose authority the call exercises; equals principal for direct calls. */
|
|
24
|
+
readonly caller: IdentityId;
|
|
24
25
|
/** Earliest expiry of all credential proofs from which this authority was derived. */
|
|
25
26
|
readonly validUntil?: number;
|
|
26
27
|
readonly attestation?: Attestation;
|
|
@@ -6,11 +6,8 @@ export type BuiltExpr = IdentityExpr | UnresolvedIdentityExpr;
|
|
|
6
6
|
export interface ExprBuilder<E extends BuiltExpr = IdentityExpr> {
|
|
7
7
|
build(): E;
|
|
8
8
|
}
|
|
9
|
-
/** Immutable
|
|
9
|
+
/** Immutable handle for one Identity leaf; there is no Identity set algebra. */
|
|
10
10
|
export interface Expr<E extends BuiltExpr = IdentityExpr> extends ExprBuilder<E> {
|
|
11
|
-
union(other: Expr<E>): Expr<E>;
|
|
12
|
-
intersect(other: Expr<E>): Expr<E>;
|
|
13
|
-
exclude(other: Expr<E>): Expr<E>;
|
|
14
11
|
}
|
|
15
12
|
export declare function isExprBuilder(input: unknown): input is ExprBuilder<IdentityExpr> | ExprBuilder<UnresolvedIdentityExpr>;
|
|
16
13
|
/** Build one resolved Identity leaf. */
|
|
@@ -19,11 +16,5 @@ export declare function identity(id: IdentityId): Expr<IdentityExpr>;
|
|
|
19
16
|
export declare function self(): Expr<UnresolvedIdentityExpr>;
|
|
20
17
|
/** Build one unresolved nested-credential leaf. */
|
|
21
18
|
export declare function cred(credential: CredentialInput): Expr<UnresolvedIdentityExpr>;
|
|
22
|
-
/** Re-enter
|
|
19
|
+
/** Re-enter from an already trusted or separately admitted Identity leaf. */
|
|
23
20
|
export declare function raw<E extends BuiltExpr>(expr: E): Expr<E>;
|
|
24
|
-
/** Compose at least two expressions of the same resolved or unresolved phase. */
|
|
25
|
-
export declare function union<E extends BuiltExpr>(first: Expr<E>, second: Expr<NoInfer<E>>, ...rest: Expr<NoInfer<E>>[]): Expr<E>;
|
|
26
|
-
/** Intersect at least two expressions of the same resolved or unresolved phase. */
|
|
27
|
-
export declare function intersect<E extends BuiltExpr>(first: Expr<E>, second: Expr<NoInfer<E>>, ...rest: Expr<NoInfer<E>>[]): Expr<E>;
|
|
28
|
-
/** Exclude one expression from another expression of the same phase. */
|
|
29
|
-
export declare function exclude<E extends BuiltExpr>(base: Expr<E>, excluded: Expr<NoInfer<E>>): Expr<E>;
|