gdp-wired 0.13.3 → 0.14.0

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/README.md CHANGED
@@ -60,9 +60,15 @@ All of these are proven automatically, and each proof is also a Lean theorem:
60
60
  - **Step laws**: what one step does ("a balance only goes down", "a grant adds one"), and laws over up to 64 steps in
61
61
  a row (`examples/steps.ts`).
62
62
  - **Histories**: sums and queries over a log of requests, like a sliding-window rate limit.
63
- - **Statecharts**: `chart(name).states({...}).on(event, {from, to, when, set})`, with nested states, parallel regions,
64
- a compound's history, guards and extended state, compiled to a rule; `check()` shows every state reached by a run (or
65
- proves it unreachable), no state stuck and no event ambiguous, and its laws prove like any rule's (`examples/charts.ts`).
63
+ - **Statecharts**: `chart(name).states({...}).on(event, {from, to, when, set})`, with states nested to any depth,
64
+ parallel regions (at the top of the chart), a compound's history (deep), guards and extended state, compiled to a rule;
65
+ `check()` shows every state reached by a run (or proves it unreachable), no state stuck and no event ambiguous, and its
66
+ laws prove like any rule's (`examples/charts.ts`). Not yet: parallel regions inside a state, entry and exit actions,
67
+ eventless and delayed transitions.
68
+ - **XState machines** (`gdp-wired/xstate`): `fromXState(machine, {guards, actions})` builds the chart an XState v5
69
+ machine runs as, with each guard and action it names given again in gdp-wired's terms, and `conforms(chart, machine,
70
+ createActor)` drives XState's own actor and the compiled rule with the same random events, comparing every state and
71
+ context; what a chart can't run the same way is refused when built, with the reason (`examples/xstate.ts`).
66
72
  - **Orderings at any distance**: "a refund never follows a release", through ghost state that watches the rule.
67
73
  - **Liveness**: "every ticket issued is served", in every run that keeps sending the requests that help, including under
68
74
  weak fairness (`examples/liveness.ts`).
@@ -372,7 +378,7 @@ The npm package is built from `core/`, `kernel/` and `lean-kernel/` alone; nothi
372
378
  ## Running
373
379
 
374
380
  The npm package is the library: the builders (`gdp-wired`), the runtimes (`gdp-wired/machine`), composition
375
- (`gdp-wired/compose`), the Lean export (`gdp-wired/lean`), the kernel
381
+ (`gdp-wired/compose`), XState machines as charts (`gdp-wired/xstate`), the Lean export (`gdp-wired/lean`), the kernel
376
382
  (`gdp-wired/kernel`), the prover (`gdp-wired/prove`), the derivation checker and solver (`gdp-wired/check`,
377
383
  `gdp-wired/solver`). Everything else here (examples, comparisons, the Lean twin, the GPU searches) runs from a clone and
378
384
  needs [Bun](https://bun.sh); Lean 4.34.0 for `lean/` and `lean-kernel/`; a WebGPU runtime (Chrome, or Dawn's node
@@ -20,6 +20,7 @@ type After<X> = X & {
20
20
  export type Transition<P, I, X> = {
21
21
  from: string | string[];
22
22
  to?: string;
23
+ fresh?: boolean;
23
24
  when?: (env: Env<P, I, X>) => Cond;
24
25
  set?: (env: Env<P, I, X>) => Partial<{
25
26
  [k in keyof X]: X[k] extends Choice<infer K> ? K | X[k] : TermLike | Cond | X[k];
@@ -119,14 +119,14 @@ export class Chart {
119
119
  for (const [k, v] of Object.entries({ ...s.context, ...s.outputs }))
120
120
  state[k] = v;
121
121
  const guardEnv = (env) => this.wrap(env);
122
- // Entering a path: a leaf, or a compound's last child (with history) or its initial one.
123
- const entered = (env, r, path) => {
122
+ // Entering a path: a leaf, or a compound's last child (with history, unless fresh) or its initial one.
123
+ const entered = (env, r, path, fresh = false) => {
124
124
  const c = r.compounds.get(path), field = env[r.field];
125
125
  if (!c)
126
126
  return field.of(path);
127
- if (c.history && path)
127
+ if (c.history && path && !fresh)
128
128
  return env[lastOf(r, path)];
129
- return entered(env, r, path ? `${path}.${c.initial}` : c.initial);
129
+ return entered(env, r, path ? `${path}.${c.initial}` : c.initial, fresh);
130
130
  };
131
131
  let built = rule(s.name).params(s.params).inputs({ event: oneOf(...events), ...s.inputs }).state(state);
132
132
  // A set of a region's states as ranges of their indices (n <= 2, 3 <= n <= 4): a few comparisons, where one test per
@@ -153,7 +153,7 @@ export class Chart {
153
153
  // The transitions taken in a region exclude one another, so those entering one state share a `when` (any of
154
154
  // them taken), and an internal one needs none (the state stays).
155
155
  out[r.field] = grouped(placed.map((t, i) => [t, i]).filter(([t]) => t.region === r.name && t.to !== undefined)
156
- .map(([t, i]) => ({ i, region: r.name, value: entered(env, r, locate(t.to)[1]) })), env, env[r.field]);
156
+ .map(([t, i]) => ({ i, region: r.name, value: entered(env, r, locate(t.to)[1], t.fresh) })), env, env[r.field]);
157
157
  for (const [c, { history }] of r.compounds)
158
158
  if (history && c)
159
159
  out[lastOf(r, c)] = when(any(...under(r, c).map(l => out[r.field].is(l))), out[r.field], env[lastOf(r, c)]);
@@ -0,0 +1,66 @@
1
+ import { type Kind, type Cond } from './dsl.js';
2
+ import { type Chart } from './chart.js';
3
+ type Obj = Record<string, any>;
4
+ type XAction = string | {
5
+ type: string;
6
+ params?: unknown;
7
+ } | ((...args: never[]) => unknown);
8
+ type XTransition = string | {
9
+ target?: string | string[];
10
+ guard?: unknown;
11
+ actions?: XAction | XAction[];
12
+ reenter?: boolean;
13
+ description?: string;
14
+ };
15
+ export type XNode = {
16
+ id?: string;
17
+ type?: 'atomic' | 'compound' | 'parallel' | 'final' | 'history';
18
+ history?: 'shallow' | 'deep';
19
+ initial?: string;
20
+ target?: string;
21
+ states?: Record<string, XNode>;
22
+ on?: Record<string, XTransition | XTransition[] | undefined>;
23
+ entry?: XAction | XAction[];
24
+ exit?: XAction | XAction[];
25
+ always?: unknown;
26
+ after?: unknown;
27
+ invoke?: unknown;
28
+ onDone?: unknown;
29
+ context?: unknown;
30
+ [k: string]: unknown;
31
+ };
32
+ export type Guard = (env: Obj, params?: unknown) => Cond;
33
+ export type Action = ((env: Obj, params?: unknown) => Obj) | null;
34
+ export type Bridge = {
35
+ params?: Record<string, Kind<unknown>>;
36
+ inputs?: Record<string, Kind<unknown>>;
37
+ context?: Record<string, number | {
38
+ kind: Kind<unknown>;
39
+ init: unknown;
40
+ }>;
41
+ guards?: Record<string, Guard>;
42
+ actions?: Record<string, Action>;
43
+ events?: string[];
44
+ };
45
+ export type MachineLike = object;
46
+ export declare function fromXState(machine: MachineLike, opts?: Bridge): Chart<Obj, Obj, Obj>;
47
+ export type XActor = {
48
+ send: (e: never) => void;
49
+ getSnapshot: () => {
50
+ value: unknown;
51
+ context?: unknown;
52
+ };
53
+ };
54
+ export declare function conforms(c: Chart<Obj, Obj, Obj>, machine: MachineLike, createActor: (machine: never) => {
55
+ start: () => XActor;
56
+ }, opts?: {
57
+ runs?: number;
58
+ depth?: number;
59
+ seed?: number;
60
+ params?: Obj;
61
+ payload?: (event: string, rand: (n: number) => number) => Obj;
62
+ }): {
63
+ checked: number;
64
+ failure?: string;
65
+ };
66
+ export {};
@@ -0,0 +1,218 @@
1
+ // XState machines as charts (core/chart.ts): `fromXState(machine, {guards, actions})` reads an XState v5 machine's
2
+ // config (states, nesting, a parallel root, history nodes, final states, transitions with named guards and actions) and
3
+ // builds the chart that runs the same way, so its laws are proven of the machine you ship. XState's guards and actions
4
+ // are JavaScript, so each one the machine names is given again in gdp-wired's terms: a guard as a condition, an action
5
+ // as the context it writes (or null, for one that writes none). `conforms(chart, machine, createActor)` then drives the
6
+ // real XState actor and the compiled rule with the same random events and compares every state and context, so a
7
+ // guard or an action given wrongly shows up as a run where the two part.
8
+ //
9
+ // const machine = setup({guards: {canRetry: ...}, actions: {count: assign(...)}}).createMachine({...})
10
+ // const c = fromXState(machine, {
11
+ // guards: {canRetry: ({retries}) => retries.lt(2)},
12
+ // actions: {count: ({retries}) => ({retries: retries.plus(1)}), notify: null},
13
+ // })
14
+ // c.check(); c.prove([c.law('retries stay within 2', ({retries}) => retries.atMost(2))])
15
+ // conforms(c, machine, createActor) // {checked, failure?}
16
+ //
17
+ // What a chart can't run the same way is refused when the chart is built, with the reason: a parallel state below the
18
+ // root, eventless (`always`) and delayed (`after`) transitions, invoked actors and done events, wildcard events, several
19
+ // targets at once, inline guards and actions, entry and exit actions that write the context, two regions writing the
20
+ // context on one event (XState runs their actions one after another; a chart's regions write in one step), and a
21
+ // shallow history over nested states.
22
+ import { Choice, bool, int } from "./dsl.js";
23
+ import { chart } from "./chart.js";
24
+ const list = (x) => (x === undefined ? [] : Array.isArray(x) ? x : [x]);
25
+ const configOf = (machine) => ('config' in machine && machine.config && typeof machine.config === 'object' ? machine.config : machine);
26
+ export function fromXState(machine, opts = {}) {
27
+ const root = configOf(machine), name = root.id ?? 'machine', parallel = root.type === 'parallel';
28
+ const refuse = (where, what) => { throw new Error(`${name}: ${where || 'the machine'}: ${what}`); };
29
+ const infos = [], ids = new Map();
30
+ const walk = (node, segs, parent) => {
31
+ const info = { node, segs, parent }, where = segs.join('.');
32
+ infos.push(info);
33
+ ids.set(node.id ?? (segs.length ? `${name}.${where}` : name), info);
34
+ for (const k of ['always', 'after', 'invoke', 'onDone'])
35
+ if (node[k] !== undefined)
36
+ refuse(where, `\`${k}\` has no chart counterpart`);
37
+ if (node.type === 'parallel' && segs.length)
38
+ refuse(where, 'a parallel state below the root (a chart runs regions only at the top)');
39
+ for (const [k, kid] of Object.entries(node.states ?? {}))
40
+ walk(kid, [...segs, k], info);
41
+ const hist = Object.entries(node.states ?? {}).filter(([, kid]) => kid.type === 'history');
42
+ if (hist.length) {
43
+ const deep = hist.every(([, h]) => h.history === 'deep'), kids = Object.entries(node.states).filter(([, kid]) => kid.type !== 'history');
44
+ if (!deep && kids.some(([, kid]) => Object.values(kid.states ?? {}).some(g => g.type !== 'history')))
45
+ refuse(where, 'a shallow history over nested states (a chart keeps the innermost state)');
46
+ for (const [k, h] of hist)
47
+ if (h.target !== undefined && h.target !== (node.initial ?? kids[0]?.[0]))
48
+ refuse(`${where}.${k}`, 'a history with its own default target');
49
+ info.history = { deep };
50
+ }
51
+ return info;
52
+ };
53
+ walk(root, []);
54
+ const pathOf = (i) => i.segs.join('.');
55
+ const descend = (from, segs, t) => segs.reduce((at, seg) => {
56
+ const kid = infos.find(x => x.parent === at && x.segs.at(-1) === seg);
57
+ return kid ?? refuse(pathOf(from), `no state ${t}`);
58
+ }, from);
59
+ // A target as XState reads it: #id(.path), .child(.path) from the state itself, or a sibling(.path).
60
+ const resolve = (from, t) => {
61
+ let at;
62
+ if (t.startsWith('#')) {
63
+ const parts = t.slice(1).split('.');
64
+ let k = parts.length;
65
+ while (k > 0 && !ids.has(parts.slice(0, k).join('.')))
66
+ k--;
67
+ if (!k)
68
+ refuse(pathOf(from), `no state with the id in ${t}`);
69
+ at = descend(ids.get(parts.slice(0, k).join('.')), parts.slice(k), t);
70
+ }
71
+ else if (t.startsWith('.'))
72
+ at = descend(from, t.slice(1).split('.'), t);
73
+ else
74
+ at = descend(from.parent ?? from, t.split('.'), t);
75
+ return at.node.type === 'history' ? { to: at.parent, viaHistory: true } : { to: at, viaHistory: false };
76
+ };
77
+ // Guards and actions, named and given again.
78
+ const guardOf = (where, g) => {
79
+ if (g === undefined)
80
+ return undefined;
81
+ const [n, params] = typeof g === 'string' ? [g, undefined] : typeof g === 'object' && g && typeof g.type === 'string' ? [g.type, g.params] : [undefined, undefined];
82
+ if (n === undefined)
83
+ return refuse(where, `an inline guard${typeof g === 'function' && g.type ? ` (${g.type})` : ''}: name it in setup({guards}) and give its condition in guards`);
84
+ const f = opts.guards?.[n] ?? refuse(where, `the guard ${n}: give its condition in guards`);
85
+ return env => f(env, params);
86
+ };
87
+ const actionsOf = (where, as) => as.flatMap(a => {
88
+ const [n, params] = typeof a === 'string' ? [a, undefined] : typeof a === 'object' && a && typeof a.type === 'string' ? [a.type, a.params] : [undefined, undefined];
89
+ if (n === undefined)
90
+ return refuse(where, `an inline action${typeof a === 'function' && a.type ? ` (${a.type})` : ''}: name it in setup({actions}) and give the context it writes in actions`);
91
+ if (!opts.actions || !(n in opts.actions))
92
+ return refuse(where, `the action ${n}: give the context it writes in actions (null if it writes none)`);
93
+ const f = opts.actions[n];
94
+ return f ? [(env) => f(env, params)] : [];
95
+ });
96
+ for (const i of infos)
97
+ for (const k of ['entry', 'exit'])
98
+ if (actionsOf(pathOf(i), list(i.node[k])).length)
99
+ refuse(pathOf(i), `${k} actions that write the context (a chart writes the context on transitions only)`);
100
+ // Several actions run one after another, each reading the context the one before wrote.
101
+ const composed = (fs) => (fs.length ? (env) => {
102
+ let cur = env;
103
+ const out = {};
104
+ for (const f of fs) {
105
+ const o = f(cur);
106
+ Object.assign(out, o);
107
+ cur = Object.assign(Object.create(null), cur, Object.fromEntries(Object.entries(o).map(([k, v]) => [k, typeof v === 'string' && cur[k] instanceof Choice ? cur[k].of(v) : v])));
108
+ }
109
+ return out;
110
+ } : undefined);
111
+ // The states, as a chart's.
112
+ const nodeOf = (i) => {
113
+ const kids = infos.filter(x => x.parent === i && x.node.type !== 'history');
114
+ if (!kids.length)
115
+ return i.node.type === 'final' ? { final: true } : {};
116
+ return { states: Object.fromEntries(kids.map(k => [k.segs.at(-1), nodeOf(k)])), ...(i.node.initial ? { initial: i.node.initial } : {}), ...(i.history ? { history: true } : {}) };
117
+ };
118
+ const top = infos.filter(x => x.parent === infos[0]);
119
+ let c = chart(name).params((opts.params ?? {})).inputs((opts.inputs ?? {})).context(contextOf(root, opts.context, refuse));
120
+ if (parallel) {
121
+ for (const r of top)
122
+ if (!infos.some(x => x.parent === r && x.node.type !== 'history'))
123
+ refuse(pathOf(r), 'a region with no states of its own');
124
+ c = c.regions(Object.fromEntries(top.map(r => [r.segs[0], nodeOf(r)])));
125
+ }
126
+ else
127
+ c = c.states(nodeOf(infos[0]).states ?? refuse('', 'no states'), root.initial ? { initial: root.initial } : {});
128
+ if (opts.events)
129
+ c = c.events(...opts.events);
130
+ // The transitions, in document order (the chart takes the most specific source first, then the first declared, as
131
+ // XState does).
132
+ const writers = new Map();
133
+ for (const i of infos)
134
+ for (const [event, spec] of Object.entries(i.node.on ?? {})) {
135
+ const where = `${pathOf(i) || 'the root'} on ${event}`;
136
+ if (event.includes('*'))
137
+ refuse(where, 'a wildcard event');
138
+ if (spec === undefined)
139
+ refuse(where, 'a forbidden event (an undefined transition)');
140
+ for (const t of list(spec)) {
141
+ const o = typeof t === 'string' ? { target: t } : t, targets = list(o.target);
142
+ if (targets.length > 1)
143
+ refuse(where, 'several targets at once');
144
+ const to = targets[0] === undefined ? undefined : resolve(i, targets[0]);
145
+ const set = composed(actionsOf(where, list(o.actions))), when = guardOf(where, o.guard);
146
+ // Where it applies: its state; for the root, every top state but a final one (XState stops there), or in a
147
+ // parallel machine the region its target is in (the first region, for one with no target).
148
+ const from = i.segs.length ? pathOf(i) : parallel ? (to ? to.to.segs[0] : top[0].segs[0]) : top.filter(x => x.node.type !== 'final' && x.node.type !== 'history').map(pathOf);
149
+ if (set) {
150
+ const region = parallel ? String(list(from)[0]).split('.')[0] : 'state';
151
+ writers.set(event, (writers.get(event) ?? new Set()).add(region));
152
+ }
153
+ c = c.on(event, { from, ...(to ? { to: pathOf(to.to), fresh: !to.viaHistory } : {}), ...(when ? { when } : {}), ...(set ? { set: set } : {}) });
154
+ }
155
+ }
156
+ for (const [event, regions] of writers)
157
+ if (regions.size > 1)
158
+ refuse(`on ${event}`, `regions ${[...regions].join(' and ')} both write the context (XState runs their actions one after another)`);
159
+ return c;
160
+ }
161
+ function contextOf(root, given, refuse) {
162
+ const out = {};
163
+ if (root.context !== undefined && typeof root.context !== 'object') {
164
+ if (!given)
165
+ refuse('', 'a context computed by a function: give its kinds in context');
166
+ return { ...given };
167
+ }
168
+ for (const [k, v] of Object.entries((root.context ?? {}))) {
169
+ if (given && k in given)
170
+ continue;
171
+ if (typeof v === 'number' && Number.isSafeInteger(v))
172
+ out[k] = v >= 0 ? v : int.from(v);
173
+ else if (typeof v === 'boolean')
174
+ out[k] = bool.from(v);
175
+ else
176
+ refuse('', `the context's ${k} (${JSON.stringify(v)}): give its kind in context`);
177
+ }
178
+ return { ...out, ...given };
179
+ }
180
+ const canonical = (v) => (v && typeof v === 'object' ? `{${Object.keys(v).sort().map(k => `${k}:${canonical(v[k])}`).join(',')}}` : JSON.stringify(v));
181
+ export function conforms(c, machine, createActor, opts = {}) {
182
+ const root = configOf(machine), events = [];
183
+ const walk = (n) => { for (const e of Object.keys(n.on ?? {}))
184
+ if (!events.includes(e))
185
+ events.push(e); Object.values(n.states ?? {}).forEach(walk); };
186
+ walk(root);
187
+ const r = c.rule, g = r.compiled(), ps = r.encodeParams((opts.params ?? {}));
188
+ // dsl.ts's generator: the high bits of a linear congruence (the low ones repeat with a short period)
189
+ let x = opts.seed ?? 1;
190
+ const rand = (k) => { x = (x * 1103515245 + 12345) % 2 ** 31; return Math.floor(x / 2 ** 31 * k); };
191
+ const valueOf = (d) => {
192
+ const nest = (path) => path.split('.').reduceRight((acc, seg) => (acc === undefined ? seg : { [seg]: acc }), undefined);
193
+ if (root.type === 'parallel')
194
+ return Object.fromEntries(Object.keys(root.states ?? {}).filter(k => root.states[k].type !== 'history').map(k => [k, nest(String(d[k]))]));
195
+ return nest(String(d.state));
196
+ };
197
+ const contextKeys = Object.keys((root.context && typeof root.context === 'object' ? root.context : {}));
198
+ let checked = 0;
199
+ for (let run = 0; run < (opts.runs ?? 100); run++) {
200
+ const actor = createActor(machine).start();
201
+ let st = g.init(ps);
202
+ const sent = [];
203
+ for (let d = 0; d < (opts.depth ?? 12); d++) {
204
+ const type = events[rand(events.length)], payload = opts.payload?.(type, rand) ?? {};
205
+ sent.push(Object.keys(payload).length ? `${type}${JSON.stringify(payload)}` : type);
206
+ actor.send({ type, ...payload });
207
+ st = g.step(ps, st, r.encodeInput({ event: type, ...payload }));
208
+ const snap = actor.getSnapshot(), d2 = r.decode(st), want = valueOf(d2);
209
+ if (canonical(snap.value) !== canonical(want))
210
+ return { checked, failure: `after ${sent.join(', ')}: XState is in ${JSON.stringify(snap.value)}, the chart in ${JSON.stringify(want)}` };
211
+ for (const k of contextKeys)
212
+ if (JSON.stringify(snap.context?.[k]) !== JSON.stringify(d2[k]))
213
+ return { checked, failure: `after ${sent.join(', ')}: XState's ${k} is ${JSON.stringify(snap.context?.[k])}, the chart's ${JSON.stringify(d2[k])}` };
214
+ checked++;
215
+ }
216
+ }
217
+ return { checked };
218
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdp-wired",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
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",
@@ -52,6 +52,10 @@
52
52
  "types": "./dist/kernel/prove.d.ts",
53
53
  "default": "./dist/kernel/prove.js"
54
54
  },
55
+ "./xstate": {
56
+ "types": "./dist/core/xstate.d.ts",
57
+ "default": "./dist/core/xstate.js"
58
+ },
55
59
  "./check": {
56
60
  "types": "./dist/core/check.d.ts",
57
61
  "default": "./dist/core/check.js"
@@ -89,7 +93,7 @@
89
93
  "build": "bun scripts/build.ts",
90
94
  "prepublishOnly": "bun scripts/build.ts",
91
95
  "smoke": "sh scripts/smoke.sh",
92
- "test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts invoices.test.ts folds.test.ts trees.test.ts pairs.test.ts chart.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/guide.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/trees.ts && bun examples/pairs.ts && bun examples/groups.ts && bun examples/charts.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
96
+ "test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts invoices.test.ts folds.test.ts trees.test.ts pairs.test.ts chart.test.ts xstate.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/guide.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/trees.ts && bun examples/pairs.ts && bun examples/groups.ts && bun examples/charts.ts && bun examples/xstate.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
93
97
  },
94
98
  "devDependencies": {
95
99
  "@gdp-ts/core": "0.1.0",
@@ -97,6 +101,7 @@
97
101
  "@webgpu/types": "0.1.66",
98
102
  "oxlint": "1.88.0",
99
103
  "oxlint-tsgolint": "7.0.2004",
100
- "typescript": "5.9.3"
104
+ "typescript": "5.9.3",
105
+ "xstate": "5.33.2"
101
106
  }
102
107
  }