@astrale-os/kernel-core 1.0.0-beta.1 → 1.0.0-beta.3

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.
@@ -8,5 +8,5 @@ export { limits } from './admission/index.js';
8
8
  export { canonicalize } from './normalization/index.js';
9
9
  export { AUTHORITY_PREDICATE_VERSION, AUTHORITY_PROGRAM_VERSION } from './language/index.js';
10
10
  export type { AuthorityExpr, AuthorityPredicate, AuthorityProgram, ClassRef, ClosedEdgeExtent, Input, LocalDeclaration, Operation, PredicateRef, Repeat, Requirement, Term, } from './language/index.js';
11
- export { AuthorityFailure, allowed, binderRoots, classAuthorityMeaning, closeComposition, createAuthorityEvaluator, fixedPaths, isFixedTerm, requiredInputs, termPath, } from './semantics/index.js';
12
- export type { AuthorityAtom, AuthorityEvaluationOptions, AuthorityEvaluator, AuthorityFailureCode, AuthorityInputs, AuthorityNode, AuthoritySnapshot, AuthorityVocabulary, CompositionClosure, CompositionLimits, CompositionStatus, FixedTerm, } from './semantics/index.js';
11
+ export { AuthorityFailure, allowed, binderRoots, classAuthorityMeaning, closeComposition, createAuthorityEvaluator, fixedPaths, isFixedTerm, requiredInputs, residualProgram, staticAtoms, termPath, } from './semantics/index.js';
12
+ export type { AuthorityAtom, AuthorityEvaluationOptions, AuthorityEvaluator, AuthorityFailureCode, AuthorityInputs, AuthorityNode, AuthoritySnapshot, AuthorityVocabulary, CompositionClosure, CompositionLimits, CompositionStatus, FixedTerm, StaticAtom, } from './semantics/index.js';
@@ -6,4 +6,4 @@ export { acceptProgram, createAuthorityProgram, isPredicateRef } from './program
6
6
  export { limits } from './admission/index.js';
7
7
  export { canonicalize } from './normalization/index.js';
8
8
  export { AUTHORITY_PREDICATE_VERSION, AUTHORITY_PROGRAM_VERSION } from './language/index.js';
9
- export { AuthorityFailure, allowed, binderRoots, classAuthorityMeaning, closeComposition, createAuthorityEvaluator, fixedPaths, isFixedTerm, requiredInputs, termPath, } from './semantics/index.js';
9
+ export { AuthorityFailure, allowed, binderRoots, classAuthorityMeaning, closeComposition, createAuthorityEvaluator, fixedPaths, isFixedTerm, requiredInputs, residualProgram, staticAtoms, termPath, } from './semantics/index.js';
@@ -28,6 +28,13 @@ export interface CompositionClosure {
28
28
  * decided. Undefined when the closure is cyclic.
29
29
  */
30
30
  status(leaf: (identity: NodeId) => boolean): CompositionStatus | undefined;
31
+ /**
32
+ * The binder's decision, `allowed` of `status`, from leaves of which some are unknown
33
+ * (undefined): the same fold in Kleene's strong three-valued logic, so a decision it returns is
34
+ * the one every value of the unknown leaves gives (`decide3_sound` of `proofs/Authority.lean`).
35
+ * Undefined when they can change it. A cyclic closure is false.
36
+ */
37
+ decide(leaf: (identity: NodeId) => boolean | undefined): boolean | undefined;
31
38
  }
32
39
  /** A binder admits its Identity when its composed status is positive and not denied. */
33
40
  export declare function allowed(status: CompositionStatus): boolean;
@@ -61,27 +61,65 @@ export function closeComposition(root, snapshot, vocabulary, identities, limits)
61
61
  status(leaf) {
62
62
  if (!acyclic)
63
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];
64
+ // Known leaves keep every value of the fold known.
65
+ const status = fold(leaf);
66
+ return Object.freeze({
67
+ full: status.full === true,
68
+ positive: status.positive === true,
69
+ denied: status.denied === true,
70
+ });
71
+ },
72
+ decide(leaf) {
73
+ if (!acyclic)
74
+ return false;
75
+ const status = fold(leaf);
76
+ return and(status.positive, not(status.denied));
83
77
  },
84
78
  });
79
+ /**
80
+ * The composed status of the root, children first, in Kleene's strong three-valued logic: an
81
+ * unknown leaf (undefined) leaves unknown only what it can change, and known leaves keep every
82
+ * value known. Iterative in the dependency order, so a deep valid composition needs no call
83
+ * stack; every member's leaf is decided once.
84
+ */
85
+ function fold(leaf) {
86
+ const values = [];
87
+ // The children's `field`, combined by `or` when `absorbing` is true and by `and` otherwise.
88
+ const combine = (list, field, absorbing) => {
89
+ let result = !absorbing;
90
+ for (const child of list) {
91
+ const truth = values[child][field];
92
+ if (truth === absorbing)
93
+ return absorbing;
94
+ if (truth === undefined)
95
+ result = undefined;
96
+ }
97
+ return result;
98
+ };
99
+ for (const ordinal of order) {
100
+ const { extensions, constraints, exclusions } = children[ordinal];
101
+ const truth = leaf(members[ordinal]);
102
+ values[ordinal] = {
103
+ full: and(and(or(truth, combine(extensions, 'full', true)), combine(constraints, 'full', false)), not(combine(exclusions, 'full', true))),
104
+ positive: and(or(truth, combine(extensions, 'positive', true)), combine(constraints, 'positive', false)),
105
+ denied: or(or(combine(exclusions, 'full', true), combine(extensions, 'denied', true)), combine(constraints, 'denied', true)),
106
+ };
107
+ }
108
+ return values[0];
109
+ }
110
+ }
111
+ function and(left, right) {
112
+ if (left === false || right === false)
113
+ return false;
114
+ return left === undefined || right === undefined ? undefined : true;
115
+ }
116
+ function or(left, right) {
117
+ if (left === true || right === true)
118
+ return true;
119
+ return left === undefined || right === undefined ? undefined : false;
120
+ }
121
+ function not(value) {
122
+ return value === undefined ? undefined : !value;
85
123
  }
86
124
  /** Kahn order over the closure; shorter than the closure exactly when it contains a cycle. */
87
125
  function childrenFirst(children) {
@@ -5,6 +5,8 @@ export { AuthorityFailure } from './failure.js';
5
5
  export type { AuthorityFailureCode } from './failure.js';
6
6
  export { binderRoots, createAuthorityEvaluator } from './interpret.js';
7
7
  export type { AuthorityAtom, AuthorityEvaluationOptions, AuthorityEvaluator } from './interpret.js';
8
+ export { residualProgram, staticAtoms } from './residual.js';
9
+ export type { StaticAtom } from './residual.js';
8
10
  export type { AuthorityInputs, AuthorityNode, AuthoritySnapshot } from './snapshot.js';
9
11
  export { fixedPaths, isFixedTerm, requiredInputs, termPath } from './terms.js';
10
12
  export type { FixedTerm } from './terms.js';
@@ -2,4 +2,5 @@ export { classAuthorityMeaning } from './class-authority.js';
2
2
  export { allowed, closeComposition } from './composition.js';
3
3
  export { AuthorityFailure } from './failure.js';
4
4
  export { binderRoots, createAuthorityEvaluator } from './interpret.js';
5
+ export { residualProgram, staticAtoms } from './residual.js';
5
6
  export { fixedPaths, isFixedTerm, requiredInputs, termPath } from './terms.js';
@@ -0,0 +1,32 @@
1
+ import type { NodeId } from '../../../graph/node/index.js';
2
+ import type { AuthorityExpr, AuthorityProgram } from '../language/index.js';
3
+ import type { CompositionClosure } from './composition.js';
4
+ import type { AuthorityAtom } from './interpret.js';
5
+ /**
6
+ * An atom outside every existential scope that reads no input: one snapshot gives it one value,
7
+ * one per subject when it names the subject of its binder (`subject`).
8
+ */
9
+ export interface StaticAtom {
10
+ readonly atom: AuthorityAtom;
11
+ readonly subject: boolean;
12
+ }
13
+ /**
14
+ * The static atoms of the expressions, each once by structure, in depth-first order: the atoms
15
+ * `residualProgram` asks to decide. An `exists` that reads no input is one atom.
16
+ */
17
+ export declare function staticAtoms(expressions: readonly AuthorityExpr[]): readonly StaticAtom[];
18
+ /**
19
+ * The program specialized to one snapshot: each static atom replaced by the value `atom` gives it,
20
+ * and each expression those values decide folded into it, in Kleene's strong three-valued logic.
21
+ * `atom` receives the subject only for an atom that names it, and undefined leaves the atom in
22
+ * place. A binder whose composition decides it from its members' specialized leaves
23
+ * (`CompositionClosure.decide`) is folded too. Otherwise it keeps the leaf every member
24
+ * specializes to alike, or the leaf specialized without a subject. `closure` is the admitted
25
+ * composition of a binder root.
26
+ *
27
+ * Once the program is admitted in that snapshot (AUTH-FAILURE-ADMITTED-EAGERLY), the residual
28
+ * decides every input frame as the program does (`pe_sound` of `proofs/Authority.lean`). Each
29
+ * member keeps its ordinal, so the residual is not canonical, and an expression nothing decides
30
+ * stays the same object.
31
+ */
32
+ export declare function residualProgram(program: AuthorityProgram, closure: (root: NodeId) => CompositionClosure | undefined, atom: (atom: AuthorityAtom, subject: NodeId | undefined) => boolean | undefined): AuthorityProgram;
@@ -0,0 +1,166 @@
1
+ import { ownTerms, requiredInputs } from './terms.js';
2
+ /**
3
+ * The static atoms of the expressions, each once by structure, in depth-first order: the atoms
4
+ * `residualProgram` asks to decide. An `exists` that reads no input is one atom.
5
+ */
6
+ export function staticAtoms(expressions) {
7
+ const found = new Map();
8
+ const visit = (value) => {
9
+ switch (value.op) {
10
+ case 'true':
11
+ case 'false':
12
+ return;
13
+ case 'all':
14
+ case 'any':
15
+ value.predicates.forEach(visit);
16
+ return;
17
+ case 'not':
18
+ visit(value.predicate);
19
+ return;
20
+ case 'as':
21
+ visit(value.where);
22
+ return;
23
+ default: {
24
+ const subject = names(value);
25
+ const key = JSON.stringify(value);
26
+ if (subject !== undefined && !found.has(key))
27
+ found.set(key, { atom: value, subject });
28
+ }
29
+ }
30
+ };
31
+ expressions.forEach(visit);
32
+ return Object.freeze([...found.values()]);
33
+ }
34
+ /**
35
+ * The program specialized to one snapshot: each static atom replaced by the value `atom` gives it,
36
+ * and each expression those values decide folded into it, in Kleene's strong three-valued logic.
37
+ * `atom` receives the subject only for an atom that names it, and undefined leaves the atom in
38
+ * place. A binder whose composition decides it from its members' specialized leaves
39
+ * (`CompositionClosure.decide`) is folded too. Otherwise it keeps the leaf every member
40
+ * specializes to alike, or the leaf specialized without a subject. `closure` is the admitted
41
+ * composition of a binder root.
42
+ *
43
+ * Once the program is admitted in that snapshot (AUTH-FAILURE-ADMITTED-EAGERLY), the residual
44
+ * decides every input frame as the program does (`pe_sound` of `proofs/Authority.lean`). Each
45
+ * member keeps its ordinal, so the residual is not canonical, and an expression nothing decides
46
+ * stays the same object.
47
+ */
48
+ export function residualProgram(program, closure, atom) {
49
+ const subjects = new Map();
50
+ const binders = new Map();
51
+ const known = (value, subject) => {
52
+ let named = subjects.get(value);
53
+ if (named === undefined)
54
+ subjects.set(value, (named = names(value) ?? null));
55
+ if (named === null)
56
+ return undefined;
57
+ if (!named)
58
+ return atom(value, undefined);
59
+ return subject === undefined ? undefined : atom(value, subject);
60
+ };
61
+ const specialize = (value, subject) => {
62
+ switch (value.op) {
63
+ case 'true':
64
+ case 'false':
65
+ return value;
66
+ case 'all':
67
+ case 'any': {
68
+ // `all` is false once one member is, `any` true once one member is.
69
+ const absorbing = value.op === 'any';
70
+ const kept = [];
71
+ let changed = false;
72
+ for (const child of value.predicates) {
73
+ const residual = specialize(child, subject);
74
+ const decided = truth(residual);
75
+ if (decided === absorbing)
76
+ return constant(absorbing);
77
+ if (decided === undefined)
78
+ kept.push(residual);
79
+ if (residual !== child || decided !== undefined)
80
+ changed = true;
81
+ }
82
+ if (!changed)
83
+ return value;
84
+ if (kept.length === 0)
85
+ return constant(!absorbing);
86
+ return kept.length === 1 ? kept[0] : Object.freeze({ op: value.op, predicates: kept });
87
+ }
88
+ case 'not': {
89
+ const residual = specialize(value.predicate, subject);
90
+ const decided = truth(residual);
91
+ if (decided !== undefined)
92
+ return constant(!decided);
93
+ return residual === value.predicate
94
+ ? value
95
+ : Object.freeze({ op: 'not', predicate: residual });
96
+ }
97
+ case 'as': {
98
+ // A binder names its own subject: its residual is the same under every outer subject.
99
+ let residual = binders.get(value);
100
+ if (residual === undefined)
101
+ binders.set(value, (residual = binder(value)));
102
+ return residual;
103
+ }
104
+ default: {
105
+ const decided = known(value, subject);
106
+ return decided === undefined ? value : constant(decided);
107
+ }
108
+ }
109
+ };
110
+ const binder = (value) => {
111
+ const composed = closure(value.identity);
112
+ if (composed === undefined)
113
+ return value;
114
+ const leaves = new Map(composed.identities.map((member) => [member, specialize(value.where, member)]));
115
+ const decided = composed.decide((member) => truth(leaves.get(member)));
116
+ if (decided !== undefined)
117
+ return constant(decided);
118
+ const [first, ...others] = leaves.values();
119
+ // Each member's leaf decides as its own specialization: one shared by every member replaces
120
+ // the leaf; otherwise only what no subject decides is specialized.
121
+ const where = others.every((leaf) => same(leaf, first))
122
+ ? first
123
+ : specialize(value.where, undefined);
124
+ return where === value.where ? value : Object.freeze({ ...value, where });
125
+ };
126
+ const predicates = program.predicates.map((predicate) => specialize(predicate, undefined));
127
+ if (predicates.every((predicate, index) => predicate === program.predicates[index])) {
128
+ return program;
129
+ }
130
+ return Object.freeze({ ...program, predicates: Object.freeze(predicates) });
131
+ }
132
+ const TRUE = Object.freeze({ op: 'true' });
133
+ const FALSE = Object.freeze({ op: 'false' });
134
+ function constant(value) {
135
+ return value ? TRUE : FALSE;
136
+ }
137
+ function truth(value) {
138
+ return value.op === 'true' ? true : value.op === 'false' ? false : undefined;
139
+ }
140
+ function same(left, right) {
141
+ return left === right || JSON.stringify(left) === JSON.stringify(right);
142
+ }
143
+ /**
144
+ * Undefined when the atom reads a decision input (`requiredInputs`); otherwise whether it names the
145
+ * subject of its binder, outside the binders nested in it.
146
+ */
147
+ function names(atom) {
148
+ if (requiredInputs([atom]).size > 0)
149
+ return undefined;
150
+ const subject = (value) => {
151
+ switch (value.op) {
152
+ case 'all':
153
+ case 'any':
154
+ return value.predicates.some(subject);
155
+ case 'not':
156
+ return subject(value.predicate);
157
+ case 'as':
158
+ return false;
159
+ case 'exists':
160
+ return subject(value.where);
161
+ default:
162
+ return ownTerms(value).some((term) => term.kind === 'subject');
163
+ }
164
+ };
165
+ return subject(atom);
166
+ }
@@ -18,3 +18,5 @@ export declare function fixedPaths(expressions: readonly AuthorityExpr[]): reado
18
18
  * `edge-class` reads. A decision whose frame leaves one of them unbound fails.
19
19
  */
20
20
  export declare function requiredInputs(expressions: readonly AuthorityExpr[]): ReadonlySet<keyof AuthorityInputs>;
21
+ /** The terms one expression reads itself, excluding those of its nested expressions. */
22
+ export declare function ownTerms(expression: AuthorityExpr): readonly Term[];
@@ -70,7 +70,7 @@ function* subexpressions(expressions) {
70
70
  }
71
71
  }
72
72
  /** The terms one expression reads itself, excluding those of its nested expressions. */
73
- function ownTerms(expression) {
73
+ export function ownTerms(expression) {
74
74
  switch (expression.op) {
75
75
  case 'same-node':
76
76
  return [expression.left, expression.right];
@@ -12,5 +12,8 @@ export function authorityRelation(target, role, description) {
12
12
  target: { as: role, accepts: [target], incoming: '0..*' },
13
13
  description,
14
14
  extends: [Edge],
15
+ // The installed authority map is observed through Identity.can, never read wholesale: only the
16
+ // Kernel Domain and Root read its Edges.
17
+ policies: { read: false, traverse: false },
15
18
  });
16
19
  }
@@ -11,12 +11,14 @@ const nodeId = {
11
11
  // mutation cannot create an Execution, change its Properties or rewrite its created_by Edge; its
12
12
  // authority Domain deletes it by ordinary mutation. `function` is a scalar, not an Edge, so deleting
13
13
  // the Function never has to detach its Executions. State and outcome are read through the engine.
14
- // No Class Policy: Runtime admits every Execution method against these facts, and a relational
15
- // read Policy here would join every polymorphic Query reaching Node or Timestamped.
14
+ // Runtime admits every Execution method against these facts; a public read sees none of them, since
15
+ // `input` carries the arguments of the call. The constant rule joins nothing, where a relational
16
+ // read Policy would join every polymorphic Query reaching Node or Timestamped.
16
17
  export const Execution = nodeClass({
17
18
  abstract: false,
18
19
  description: 'One accepted durable call of a Function, recorded as immutable facts.',
19
20
  extends: [Node, Timestamped],
21
+ policies: { read: false, traverse: false },
20
22
  properties: {
21
23
  function: nodeId,
22
24
  version: { type: 'string', fullMatch: 'sha256:[0-9a-f]{64}' },
@@ -9446,17 +9446,6 @@ export declare const KernelRootFunctions: readonly ((import("@astrale-os/kernel-
9446
9446
  };
9447
9447
  readonly key: "kernel.astrale.ai:function.upload";
9448
9448
  readonly execution: undefined;
9449
- }) | (import("@astrale-os/kernel-dsl/v1").ResolvedMethod<{
9450
- audience?: string | undefined;
9451
- ttlSeconds: number;
9452
- identity: string;
9453
- }, {
9454
- readonly mode: 'value';
9455
- readonly value: string;
9456
- }, true, false, "authorized", import("@astrale-os/kernel-dsl/v1").CallablePolicy, "kernel.astrale.ai:class.Identity.method.impersonate"> & {
9457
- readonly name: "impersonate";
9458
- readonly executable: true;
9459
- readonly execution: undefined;
9460
9449
  }) | (import("@astrale-os/kernel-dsl/v1").ResolvedMethod<{
9461
9450
  iss: string;
9462
9451
  sub: string;
@@ -9516,6 +9505,17 @@ export declare const KernelRootFunctions: readonly ((import("@astrale-os/kernel-
9516
9505
  readonly name: "resolve";
9517
9506
  readonly executable: true;
9518
9507
  readonly execution: undefined;
9508
+ }) | (import("@astrale-os/kernel-dsl/v1").ResolvedMethod<{
9509
+ audience?: string | undefined;
9510
+ ttlSeconds: number;
9511
+ identity: string;
9512
+ }, {
9513
+ readonly mode: 'value';
9514
+ readonly value: string;
9515
+ }, true, false, "authorized", import("@astrale-os/kernel-dsl/v1").CallablePolicy, "kernel.astrale.ai:class.Identity.method.impersonate"> & {
9516
+ readonly name: "impersonate";
9517
+ readonly executable: true;
9518
+ readonly execution: undefined;
9519
9519
  }))[];
9520
9520
  export { AuthorityOperationOrder, AuthorityOperations, operationOf } from '../authority/index.js';
9521
9521
  export type { AuthorityOperation, AuthorityRelation } from '../authority/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-core",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "Astrale Kernel Core - Types, Invariants, and Pure Logic",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -160,7 +160,7 @@
160
160
  "access": "public"
161
161
  },
162
162
  "dependencies": {
163
- "@astrale-os/kernel-dsl": "1.0.0-beta.1",
163
+ "@astrale-os/kernel-dsl": "1.0.0-beta.2",
164
164
  "@noble/hashes": "2.3.0",
165
165
  "@types/json-schema": "^7.0.15",
166
166
  "dequal": "2.0.3"