gdp-wired 0.13.0 → 0.13.1
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/core/chart.d.ts +16 -3
- package/dist/core/chart.js +44 -15
- package/package.json +1 -1
package/dist/core/chart.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Cond, type Kind, type Rule, type Term, type TermLike, type Verdict,
|
|
1
|
+
import { type Cond, type Kind, type Rule, type Term, type TermLike, type Verdict, Choice } from './dsl.js';
|
|
2
2
|
import { type Law } from './machine.js';
|
|
3
3
|
import type { StepLaw } from '../kernel/kernel.js';
|
|
4
4
|
export type StateNode = {
|
|
@@ -19,10 +19,10 @@ type After<X> = X & {
|
|
|
19
19
|
};
|
|
20
20
|
export type Transition<P, I, X> = {
|
|
21
21
|
from: string | string[];
|
|
22
|
-
to
|
|
22
|
+
to?: string;
|
|
23
23
|
when?: (env: Env<P, I, X>) => Cond;
|
|
24
24
|
set?: (env: Env<P, I, X>) => Partial<{
|
|
25
|
-
[k in keyof X]: TermLike | Cond | X[k];
|
|
25
|
+
[k in keyof X]: X[k] extends Choice<infer K> ? K | X[k] : TermLike | Cond | X[k];
|
|
26
26
|
}>;
|
|
27
27
|
};
|
|
28
28
|
export declare class Chart<P extends Obj = {}, I extends Obj = {}, X extends Obj = {}> {
|
|
@@ -32,8 +32,11 @@ export declare class Chart<P extends Obj = {}, I extends Obj = {}, X extends Obj
|
|
|
32
32
|
params: Obj;
|
|
33
33
|
inputs: Obj;
|
|
34
34
|
context: Obj;
|
|
35
|
+
outputs: Obj;
|
|
35
36
|
regions: [string, StateNode][];
|
|
36
37
|
on: [string, Transition<P, I, X>][];
|
|
38
|
+
field?: string;
|
|
39
|
+
events?: string[];
|
|
37
40
|
});
|
|
38
41
|
private with;
|
|
39
42
|
params<K extends Record<string, Kind<unknown>>>(kinds: K): Chart<P & {
|
|
@@ -50,8 +53,18 @@ export declare class Chart<P extends Obj = {}, I extends Obj = {}, X extends Obj
|
|
|
50
53
|
kind: Kind<infer V>;
|
|
51
54
|
} ? V : Term;
|
|
52
55
|
}>;
|
|
56
|
+
outputs<T extends Record<string, number | {
|
|
57
|
+
kind: Kind<unknown>;
|
|
58
|
+
init: unknown;
|
|
59
|
+
}>>(init: T): Chart<P, I, X & {
|
|
60
|
+
[k in keyof T]: T[k] extends {
|
|
61
|
+
kind: Kind<infer V>;
|
|
62
|
+
} ? V : Term;
|
|
63
|
+
}>;
|
|
64
|
+
events(...names: string[]): Chart<P, I, X>;
|
|
53
65
|
states(tree: Record<string, StateNode>, opts?: {
|
|
54
66
|
initial?: string;
|
|
67
|
+
field?: string;
|
|
55
68
|
}): Chart<P, I, X>;
|
|
56
69
|
regions(regions: Record<string, StateNode>): Chart<P, I, X>;
|
|
57
70
|
on(event: string, t: Transition<P, I, X>): Chart<P, I, X>;
|
package/dist/core/chart.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// most specific source (an inner state over its parent) is taken, then the first declared; the regions react to the
|
|
17
17
|
// same event at once. A transition into a compound state enters its initial child, or the child it was last in when the
|
|
18
18
|
// compound keeps its history; `set` writes the extended state (a variable two regions both set takes the first region's).
|
|
19
|
-
import { all, any, oneOf, proveRule, proveSteps, rule, when } from "./dsl.js";
|
|
19
|
+
import { Choice, all, any, no, oneOf, proveRule, proveSteps, rule, when, yes } from "./dsl.js";
|
|
20
20
|
import { interpret, numbersOf } from "./machine.js";
|
|
21
21
|
import { allowed } from "./watch.js";
|
|
22
22
|
export class Chart {
|
|
@@ -30,8 +30,15 @@ export class Chart {
|
|
|
30
30
|
inputs(kinds) { return this.with({ inputs: { ...this.s.inputs, ...kinds } }); }
|
|
31
31
|
// The extended state: a natural from a number, or any kind from `kind.from(init)`.
|
|
32
32
|
context(init) { return this.with({ context: { ...this.s.context, ...init } }); }
|
|
33
|
-
//
|
|
34
|
-
|
|
33
|
+
// What each step decides, read after it (a command, a verdict): every output starts each step at its default, and a
|
|
34
|
+
// transition taken may set it, so a step where none fires reads the default, never the last step's value. A default
|
|
35
|
+
// is a number, a choice (`oneOf(...).from('denied')`) or a yes/no (`bool.from(false)`).
|
|
36
|
+
outputs(init) { return this.with({ outputs: { ...this.s.outputs, ...init } }); }
|
|
37
|
+
// The events, in the order they are encoded (by default, the order they are first used in `on`).
|
|
38
|
+
events(...names) { return this.with({ events: names }); }
|
|
39
|
+
// One region's states (a chart of one region names its states plainly: 'active.paid'); `field` names the state
|
|
40
|
+
// variable the rule keeps them in (`state` by default).
|
|
41
|
+
states(tree, opts = {}) { return this.with({ regions: [['state', { states: tree, initial: opts.initial }]], field: opts.field }); }
|
|
35
42
|
// Parallel regions, each with its own states, reacting to the same events: paths start with the region ('payment.paid').
|
|
36
43
|
regions(regions) { return this.with({ regions: Object.entries(regions) }); }
|
|
37
44
|
on(event, t) { return this.with({ on: [...this.s.on, [event, t]] }); }
|
|
@@ -64,7 +71,7 @@ export class Chart {
|
|
|
64
71
|
}
|
|
65
72
|
};
|
|
66
73
|
walk('', root);
|
|
67
|
-
return { name, field: single ? 'state' : name, leaves, finals, compounds };
|
|
74
|
+
return { name, field: single ? s.field ?? 'state' : name, leaves, finals, compounds };
|
|
68
75
|
});
|
|
69
76
|
// A path as the region and the state within it.
|
|
70
77
|
const locate = (path) => {
|
|
@@ -81,14 +88,20 @@ export class Chart {
|
|
|
81
88
|
throw new Error(`${s.name}: no state ${path} in ${r.name}`);
|
|
82
89
|
return out;
|
|
83
90
|
};
|
|
84
|
-
const
|
|
91
|
+
const used = [...new Set(s.on.map(([e]) => e))], events = s.events ?? used;
|
|
92
|
+
for (const e of used)
|
|
93
|
+
if (!events.includes(e))
|
|
94
|
+
throw new Error(`${s.name}: the event ${e} isn't one of the events declared (${events.join(', ')})`);
|
|
95
|
+
if (new Set(events).size !== events.length)
|
|
96
|
+
throw new Error(`${s.name}: an event is declared twice`);
|
|
85
97
|
if (!events.length)
|
|
86
98
|
throw new Error(`${s.name}: a chart needs transitions`);
|
|
87
99
|
const placed = s.on.map(([event, t], order) => {
|
|
88
|
-
const froms = (Array.isArray(t.from) ? t.from : [t.from]).map(locate), [to] = locate(t.to);
|
|
100
|
+
const froms = (Array.isArray(t.from) ? t.from : [t.from]).map(locate), [to] = t.to === undefined ? froms[0] : locate(t.to);
|
|
89
101
|
if (froms.some(([r]) => r !== to))
|
|
90
|
-
throw new Error(`${s.name}: a transition on ${event} goes from one region to another (${t.to})`);
|
|
91
|
-
|
|
102
|
+
throw new Error(`${s.name}: a transition on ${event} goes from one region to another (${t.to ?? [t.from].flat().join(", ")})`);
|
|
103
|
+
if (t.to !== undefined)
|
|
104
|
+
under(to, locate(t.to)[1]);
|
|
92
105
|
const sources = [...new Set(froms.flatMap(([r, p]) => under(r, p)))];
|
|
93
106
|
return { ...t, event, region: to.name, sources, depth: Math.min(...froms.map(([, p]) => (p === '' ? 0 : p.split('.').length))), order };
|
|
94
107
|
});
|
|
@@ -103,7 +116,7 @@ export class Chart {
|
|
|
103
116
|
if (history && c)
|
|
104
117
|
state[lastOf(r, c)] = kind.from(enter(c));
|
|
105
118
|
}
|
|
106
|
-
for (const [k, v] of Object.entries(s.context))
|
|
119
|
+
for (const [k, v] of Object.entries({ ...s.context, ...s.outputs }))
|
|
107
120
|
state[k] = v;
|
|
108
121
|
const inState = (env) => (path) => { const [r, p] = locate(path); return any(...under(r, p).map(l => env[r.field].is(l))); };
|
|
109
122
|
const guardEnv = (env) => this.wrap(env);
|
|
@@ -125,15 +138,17 @@ export class Chart {
|
|
|
125
138
|
for (const r of regions) {
|
|
126
139
|
// The transitions taken in a region exclude one another: a chain of `when`s, the first in order outermost.
|
|
127
140
|
out[r.field] = placed.map((t, i) => [t, i]).filter(([t]) => t.region === r.name).reverse()
|
|
128
|
-
.reduce((acc, [t, i]) => when(env[`take${i}`], entered(env, r, locate(t.to)[1]), acc), env[r.field]);
|
|
141
|
+
.reduce((acc, [t, i]) => when(env[`take${i}`], t.to === undefined ? env[r.field] : entered(env, r, locate(t.to)[1]), acc), env[r.field]);
|
|
129
142
|
for (const [c, { history }] of r.compounds)
|
|
130
143
|
if (history && c)
|
|
131
144
|
out[lastOf(r, c)] = when(any(...under(r, c).map(l => out[r.field].is(l))), out[r.field], env[lastOf(r, c)]);
|
|
132
145
|
}
|
|
133
|
-
|
|
146
|
+
// Context keeps its value where no transition taken sets it; an output falls back to its default.
|
|
147
|
+
for (const k of [...Object.keys(s.context), ...Object.keys(s.outputs)]) {
|
|
134
148
|
const sets = placed.map((t, i) => [t, i]).filter(([t]) => t.set).map(([t, i]) => [i, t.set(guardEnv(env))[k]]).filter(([, v]) => v !== undefined);
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
const base = k in s.outputs ? defaultOf(env[k], s.outputs[k], k) : env[k];
|
|
150
|
+
if (sets.length || k in s.outputs)
|
|
151
|
+
out[k] = sets.reverse().reduce((acc, [i, v]) => when(env[`take${i}`], v, acc), base);
|
|
137
152
|
}
|
|
138
153
|
return out;
|
|
139
154
|
});
|
|
@@ -225,7 +240,8 @@ export class Chart {
|
|
|
225
240
|
const v = proveRule(r, [...this.chartLaws(), never]);
|
|
226
241
|
reached[name] = v.proven && v.checked ? 'unreachable' : 'unknown';
|
|
227
242
|
}
|
|
228
|
-
|
|
243
|
+
// A way out is a transition to a state: an internal one stays where it is.
|
|
244
|
+
const stuck = regions.flatMap(reg => reg.leaves.filter(l => !reg.finals.has(l) && !placed.some(t => t.to !== undefined && t.region === reg.name && t.sources.includes(l))).map(l => label(reg, l)));
|
|
229
245
|
const ambiguous = [];
|
|
230
246
|
placed.forEach((t, i) => placed.forEach((u, j) => {
|
|
231
247
|
if (j <= i || t.region !== u.region || t.event !== u.event || t.depth !== u.depth || !t.sources.some(l => u.sources.includes(l)))
|
|
@@ -238,4 +254,17 @@ export class Chart {
|
|
|
238
254
|
return { ok: !stuck.length && !ambiguous.length && Object.values(reached).every(x => x !== 'unknown'), reached, stuck, ambiguous };
|
|
239
255
|
}
|
|
240
256
|
}
|
|
241
|
-
export const chart = (name) => new Chart({ name, params: {}, inputs: {}, context: {}, regions: [], on: [] });
|
|
257
|
+
export const chart = (name) => new Chart({ name, params: {}, inputs: {}, context: {}, outputs: {}, regions: [], on: [] });
|
|
258
|
+
// An output's default as a value of its kind: a number, a choice's named value, or a yes/no.
|
|
259
|
+
function defaultOf(current, init, name) {
|
|
260
|
+
if (typeof init === 'number')
|
|
261
|
+
return init;
|
|
262
|
+
const { kind, init: v } = init;
|
|
263
|
+
if (current instanceof Choice)
|
|
264
|
+
return current.of(v);
|
|
265
|
+
if (typeof v === 'boolean')
|
|
266
|
+
return v ? yes : no;
|
|
267
|
+
if (typeof v === 'number')
|
|
268
|
+
return v;
|
|
269
|
+
throw new Error(`the output ${name}'s default is a number, a choice or a yes/no, not ${String(v)} of ${kind.constructor.name}`);
|
|
270
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "An automatic prover for stateful rules (budgets, caps, windows, workflows, policies): laws proven with no proof to write, checked again in Lean, counterexamples found on the GPU, the rule shipped as code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"verification",
|