@astrale-os/kernel-core 1.0.0-beta.2 → 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];
@@ -8832,24 +8832,38 @@ export declare const KernelRootFunctions: readonly ((import("@astrale-os/kernel-
8832
8832
  };
8833
8833
  readonly key: "kernel.astrale.ai:function.install";
8834
8834
  readonly execution: undefined;
8835
- }) | (import("@astrale-os/kernel-dsl/v1").ResolvedFunction<import("@astrale-os/kernel-dsl/v1").BinaryInputProjection<{
8836
- mediaType: string;
8837
- }>, {
8835
+ }) | (import("@astrale-os/kernel-dsl/v1").ResolvedFunction<{
8836
+ invocation?: {
8837
+ source: string;
8838
+ id: string;
8839
+ } | undefined;
8840
+ topics?: {
8841
+ exact?: string[] | undefined;
8842
+ prefixes?: string[] | undefined;
8843
+ } | undefined;
8844
+ principal?: string | undefined;
8845
+ since?: string | undefined;
8846
+ until?: string | undefined;
8847
+ cursor?: string | undefined;
8848
+ limit?: number | undefined;
8849
+ }, {
8838
8850
  readonly mode: 'value';
8839
8851
  readonly value: {
8840
- receipt: string;
8841
- ref: {
8842
- digest: string;
8843
- mediaType: string;
8844
- size: number;
8852
+ records: unknown[];
8853
+ frontier: {
8854
+ id: string;
8855
+ first?: number | undefined;
8856
+ committed: number;
8857
+ durable: number;
8845
8858
  };
8846
- expiresAt: string;
8859
+ cursor?: string | undefined;
8860
+ gap?: unknown;
8847
8861
  };
8848
8862
  }, "authorized", import("@astrale-os/kernel-dsl/v1").CallablePolicy> & {
8849
- readonly ref: import("@astrale-os/kernel-dsl/v1").FunctionRef<"upload"> & {
8863
+ readonly ref: import("@astrale-os/kernel-dsl/v1").FunctionRef<"journal"> & {
8850
8864
  readonly origin: "kernel.astrale.ai";
8851
8865
  };
8852
- readonly key: "kernel.astrale.ai:function.upload";
8866
+ readonly key: "kernel.astrale.ai:function.journal";
8853
8867
  readonly execution: undefined;
8854
8868
  }) | (import("@astrale-os/kernel-dsl/v1").ResolvedFunction<{
8855
8869
  ast: {
@@ -9413,38 +9427,24 @@ export declare const KernelRootFunctions: readonly ((import("@astrale-os/kernel-
9413
9427
  };
9414
9428
  readonly key: "kernel.astrale.ai:function.uninstall";
9415
9429
  readonly execution: undefined;
9416
- }) | (import("@astrale-os/kernel-dsl/v1").ResolvedFunction<{
9417
- invocation?: {
9418
- source: string;
9419
- id: string;
9420
- } | undefined;
9421
- topics?: {
9422
- exact?: string[] | undefined;
9423
- prefixes?: string[] | undefined;
9424
- } | undefined;
9425
- principal?: string | undefined;
9426
- since?: string | undefined;
9427
- until?: string | undefined;
9428
- cursor?: string | undefined;
9429
- limit?: number | undefined;
9430
- }, {
9430
+ }) | (import("@astrale-os/kernel-dsl/v1").ResolvedFunction<import("@astrale-os/kernel-dsl/v1").BinaryInputProjection<{
9431
+ mediaType: string;
9432
+ }>, {
9431
9433
  readonly mode: 'value';
9432
9434
  readonly value: {
9433
- records: unknown[];
9434
- frontier: {
9435
- id: string;
9436
- first?: number | undefined;
9437
- committed: number;
9438
- durable: number;
9435
+ receipt: string;
9436
+ ref: {
9437
+ digest: string;
9438
+ mediaType: string;
9439
+ size: number;
9439
9440
  };
9440
- cursor?: string | undefined;
9441
- gap?: unknown;
9441
+ expiresAt: string;
9442
9442
  };
9443
9443
  }, "authorized", import("@astrale-os/kernel-dsl/v1").CallablePolicy> & {
9444
- readonly ref: import("@astrale-os/kernel-dsl/v1").FunctionRef<"journal"> & {
9444
+ readonly ref: import("@astrale-os/kernel-dsl/v1").FunctionRef<"upload"> & {
9445
9445
  readonly origin: "kernel.astrale.ai";
9446
9446
  };
9447
- readonly key: "kernel.astrale.ai:function.journal";
9447
+ readonly key: "kernel.astrale.ai:function.upload";
9448
9448
  readonly execution: undefined;
9449
9449
  }) | (import("@astrale-os/kernel-dsl/v1").ResolvedMethod<{
9450
9450
  iss: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-core",
3
- "version": "1.0.0-beta.2",
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": {