gdp-wired 0.13.1 → 0.13.2
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 +1 -1
- package/dist/core/chart.js +69 -23
- package/dist/core/dsl.d.ts +4 -1
- package/dist/core/dsl.js +27 -4
- package/package.json +1 -1
package/dist/core/chart.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Kind, type Rule, type TermLike, type Verdict, Choice, Cond, Term } 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 = {
|
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 { Choice, all, any, no, oneOf, proveRule, proveSteps, rule, when, yes } from "./dsl.js";
|
|
19
|
+
import { Choice, Cond, Term, 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 {
|
|
@@ -118,7 +118,6 @@ export class Chart {
|
|
|
118
118
|
}
|
|
119
119
|
for (const [k, v] of Object.entries({ ...s.context, ...s.outputs }))
|
|
120
120
|
state[k] = v;
|
|
121
|
-
const inState = (env) => (path) => { const [r, p] = locate(path); return any(...under(r, p).map(l => env[r.field].is(l))); };
|
|
122
121
|
const guardEnv = (env) => this.wrap(env);
|
|
123
122
|
// Entering a path: a leaf, or a compound's last child (with history) or its initial one.
|
|
124
123
|
const entered = (env, r, path) => {
|
|
@@ -130,25 +129,41 @@ export class Chart {
|
|
|
130
129
|
return entered(env, r, path ? `${path}.${c.initial}` : c.initial);
|
|
131
130
|
};
|
|
132
131
|
let built = rule(s.name).params(s.params).inputs({ event: oneOf(...events), ...s.inputs }).state(state);
|
|
133
|
-
|
|
134
|
-
//
|
|
135
|
-
|
|
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
|
|
133
|
+
// state would be a disjunction the prover splits on; the whole region is no condition at all.
|
|
134
|
+
const among = (env, r, leaves) => {
|
|
135
|
+
const n = env[r.field].index, ix = leaves.map(l => r.leaves.indexOf(l)).sort((a, b) => a - b), top = r.leaves.length - 1, ranges = [];
|
|
136
|
+
for (const i of ix) {
|
|
137
|
+
const last = ranges.at(-1);
|
|
138
|
+
if (last && i === last[1] + 1)
|
|
139
|
+
last[1] = i;
|
|
140
|
+
else
|
|
141
|
+
ranges.push([i, i]);
|
|
142
|
+
}
|
|
143
|
+
return any(...ranges.map(([a, b]) => (a === 0 && b === top ? yes : a === b ? n.eq(a) : a === 0 ? n.atMost(b) : b === top ? n.atLeast(a) : n.atLeast(a).and(n.atMost(b)))));
|
|
144
|
+
};
|
|
145
|
+
const regionOf = (t) => regions.find(r => r.name === t.region);
|
|
146
|
+
built = built.flags((env) => Object.fromEntries(placed.map((t, i) => [`on${i}`, all(env.event.is(t.event), among(env, regionOf(t), t.sources), ...(t.when ? [t.when(guardEnv(env))] : []))])));
|
|
147
|
+
// A transition is taken when it is enabled and no transition before it in its region, on its event, from a state it
|
|
148
|
+
// shares, is (transitions on other events are never enabled together).
|
|
149
|
+
built = built.flags((env) => Object.fromEntries(placed.map((t, i) => [`take${i}`, all(env[`on${i}`], ...placed.slice(0, i).flatMap((u, j) => (u.region === t.region && u.event === t.event && u.sources.some(l => t.sources.includes(l)) ? [env[`on${j}`].not()] : [])))])));
|
|
136
150
|
built = built.step((env) => {
|
|
137
151
|
const out = {};
|
|
138
152
|
for (const r of regions) {
|
|
139
|
-
// The transitions taken in a region exclude one another
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
// The transitions taken in a region exclude one another, so those entering one state share a `when` (any of
|
|
154
|
+
// them taken), and an internal one needs none (the state stays).
|
|
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]);
|
|
142
157
|
for (const [c, { history }] of r.compounds)
|
|
143
158
|
if (history && c)
|
|
144
159
|
out[lastOf(r, c)] = when(any(...under(r, c).map(l => out[r.field].is(l))), out[r.field], env[lastOf(r, c)]);
|
|
145
160
|
}
|
|
146
161
|
// Context keeps its value where no transition taken sets it; an output falls back to its default.
|
|
147
162
|
for (const k of [...Object.keys(s.context), ...Object.keys(s.outputs)]) {
|
|
148
|
-
const sets = placed.map((t, i) => [t, i]).filter(([t]) => t.set).map(([t, i]) =>
|
|
163
|
+
const sets = placed.map((t, i) => [t, i]).filter(([t]) => t.set).map(([t, i]) => ({ i, region: t.region, value: t.set(guardEnv(env))[k] })).filter(x => x.value !== undefined);
|
|
149
164
|
const base = k in s.outputs ? defaultOf(env[k], s.outputs[k], k) : env[k];
|
|
150
165
|
if (sets.length || k in s.outputs)
|
|
151
|
-
out[k] = sets.
|
|
166
|
+
out[k] = grouped(sets.map(x => ({ ...x, value: typeof x.value === 'string' ? env[k].of(x.value) : x.value })), env, base);
|
|
152
167
|
}
|
|
153
168
|
return out;
|
|
154
169
|
});
|
|
@@ -213,7 +228,9 @@ export class Chart {
|
|
|
213
228
|
reachedAt.set(label(reg, l), k);
|
|
214
229
|
} };
|
|
215
230
|
note(queue[0], start);
|
|
216
|
-
|
|
231
|
+
// Breadth first, so each state's run is a shortest one; done once every state has one.
|
|
232
|
+
const states = regions.reduce((n, reg) => n + reg.leaves.length, 0);
|
|
233
|
+
while (queue.length && seen.size < (opts.maxStates ?? 20_000) && reachedAt.size < states) {
|
|
217
234
|
const k = queue.shift(), st = seen.get(k).st;
|
|
218
235
|
for (const req of requests) {
|
|
219
236
|
if (!allowed(m, run, params, st, req))
|
|
@@ -228,32 +245,61 @@ export class Chart {
|
|
|
228
245
|
}
|
|
229
246
|
const runTo = (k) => { const out = []; for (let at = seen.get(k); at.by; at = seen.get(at.from))
|
|
230
247
|
out.unshift({ ...numbersOf(at.by), event: events[at.by.event] }); return out; };
|
|
231
|
-
|
|
248
|
+
// States no run reached: proven unreachable together (one may lean on another), else each on its own.
|
|
249
|
+
const reached = {}, missed = [];
|
|
232
250
|
for (const reg of regions)
|
|
233
251
|
for (const l of reg.leaves) {
|
|
234
252
|
const name = label(reg, l), at = reachedAt.get(name);
|
|
235
|
-
if (at)
|
|
253
|
+
if (at)
|
|
236
254
|
reached[name] = { run: runTo(at) };
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const never = r.law(`${name} is never reached`, (env) => env[reg.field].is(l).not());
|
|
240
|
-
const v = proveRule(r, [...this.chartLaws(), never]);
|
|
241
|
-
reached[name] = v.proven && v.checked ? 'unreachable' : 'unknown';
|
|
255
|
+
else
|
|
256
|
+
missed.push({ name, law: r.law(`${name} is never reached`, (env) => env[reg.field].is(l).not()) });
|
|
242
257
|
}
|
|
258
|
+
const proven = (v) => v.proven && v.checked;
|
|
259
|
+
const allMissed = missed.length > 0 && proven(proveRule(r, [...this.chartLaws(), ...missed.map(x => x.law)]));
|
|
260
|
+
for (const x of missed)
|
|
261
|
+
reached[x.name] = allMissed || proven(proveRule(r, [...this.chartLaws(), x.law])) ? 'unreachable' : 'unknown';
|
|
243
262
|
// A way out is a transition to a state: an internal one stays where it is.
|
|
244
263
|
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)));
|
|
245
|
-
|
|
264
|
+
// Pairs as specific as each other, on one event, from a state they share: proven never both enabled, all at once,
|
|
265
|
+
// and only where that fails each on its own (to name the pair and the request).
|
|
266
|
+
const pairs = [];
|
|
246
267
|
placed.forEach((t, i) => placed.forEach((u, j) => {
|
|
247
268
|
if (j <= i || t.region !== u.region || t.event !== u.event || t.depth !== u.depth || !t.sources.some(l => u.sources.includes(l)))
|
|
248
269
|
return;
|
|
249
|
-
|
|
250
|
-
const v = proveSteps(r, this.chartLaws(), [apart]);
|
|
251
|
-
if (!(v.proven && v.checked))
|
|
252
|
-
ambiguous.push({ event: t.event, transitions: [t.order, u.order], verdict: v });
|
|
270
|
+
pairs.push({ t, u, law: r.stepLaw(`transitions ${t.order} and ${u.order} on ${t.event} are never both enabled`, (env) => all(env[`on${i}`], env[`on${j}`]).not()) });
|
|
253
271
|
}));
|
|
272
|
+
const ambiguous = [];
|
|
273
|
+
if (pairs.length && !proven(proveSteps(r, this.chartLaws(), pairs.map(x => x.law))))
|
|
274
|
+
for (const { t, u, law } of pairs) {
|
|
275
|
+
const v = proveSteps(r, this.chartLaws(), [law]);
|
|
276
|
+
if (!proven(v))
|
|
277
|
+
ambiguous.push({ event: t.event, transitions: [t.order, u.order], verdict: v });
|
|
278
|
+
}
|
|
254
279
|
return { ok: !stuck.length && !ambiguous.length && Object.values(reached).every(x => x !== 'unknown'), reached, stuck, ambiguous };
|
|
255
280
|
}
|
|
256
281
|
}
|
|
282
|
+
// The value a step writes: of the transitions taken, the first in order that writes one. Transitions of one region exclude
|
|
283
|
+
// one another, so consecutive ones of a region writing the same value share one `when` (any of them taken); the order
|
|
284
|
+
// between regions is kept.
|
|
285
|
+
function grouped(writes, env, base) {
|
|
286
|
+
const keyOf = (v) => JSON.stringify(v instanceof Choice ? v.index.e : v instanceof Cond ? v.c : v instanceof Term ? v.e : v);
|
|
287
|
+
const groups = [];
|
|
288
|
+
for (const w of writes) {
|
|
289
|
+
const key = keyOf(w.value);
|
|
290
|
+
let g;
|
|
291
|
+
for (let j = groups.length - 1; j >= 0 && groups[j].region === w.region; j--)
|
|
292
|
+
if (groups[j].key === key) {
|
|
293
|
+
g = groups[j];
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (g)
|
|
297
|
+
g.takes.push(env[`take${w.i}`]);
|
|
298
|
+
else
|
|
299
|
+
groups.push({ region: w.region, key, value: w.value, takes: [env[`take${w.i}`]] });
|
|
300
|
+
}
|
|
301
|
+
return groups.reduceRight((acc, g) => when(any(...g.takes), g.value, acc), base);
|
|
302
|
+
}
|
|
257
303
|
export const chart = (name) => new Chart({ name, params: {}, inputs: {}, context: {}, outputs: {}, regions: [], on: [] });
|
|
258
304
|
// An output's default as a value of its kind: a number, a choice's named value, or a yes/no.
|
|
259
305
|
function defaultOf(current, init, name) {
|
package/dist/core/dsl.d.ts
CHANGED
|
@@ -131,7 +131,10 @@ export declare class Choice<K extends string> {
|
|
|
131
131
|
constructor(n: Term, values: readonly K[]);
|
|
132
132
|
is(k: K): Cond;
|
|
133
133
|
of(k: K): Choice<K>;
|
|
134
|
-
|
|
134
|
+
get index(): Term;
|
|
135
|
+
eq(o: K | Choice<K> | number): Cond;
|
|
136
|
+
ne(o: K | Choice<K> | number): Cond;
|
|
137
|
+
private indexOf;
|
|
135
138
|
match(cases: {
|
|
136
139
|
[k in K]: () => Cond;
|
|
137
140
|
}): Cond;
|
package/dist/core/dsl.js
CHANGED
|
@@ -353,10 +353,15 @@ export class Choice {
|
|
|
353
353
|
this.n = n;
|
|
354
354
|
this.values = values;
|
|
355
355
|
}
|
|
356
|
-
is(k) { return this.n.eq(this.
|
|
356
|
+
is(k) { return this.n.eq(this.indexOf(k)); }
|
|
357
357
|
// The value named k, of this choice's kind (to write: when(c, status.of('closed'), status)).
|
|
358
|
-
of(k) { return new Choice(lit(this.
|
|
359
|
-
index(
|
|
358
|
+
of(k) { return new Choice(lit(this.indexOf(k)), this.values); }
|
|
359
|
+
// Its index among the values, in the order declared (oneOf('new', 'paid'): new is 0), as a term.
|
|
360
|
+
get index() { return this.n; }
|
|
361
|
+
// The same value as a name, another choice of the kind, or an index: status.eq('paid'), a.phase.eq(b.phase), phase.eq(3).
|
|
362
|
+
eq(o) { return typeof o === 'string' ? this.is(o) : typeof o === 'number' ? this.n.eq(o) : this.n.eq(o.n); }
|
|
363
|
+
ne(o) { return this.eq(o).not(); }
|
|
364
|
+
indexOf(k) {
|
|
360
365
|
const i = this.values.indexOf(k);
|
|
361
366
|
if (i < 0)
|
|
362
367
|
throw new Error(`${k} is not one of ${this.values.join(', ')}`);
|
|
@@ -2426,13 +2431,31 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2426
2431
|
st = next;
|
|
2427
2432
|
}
|
|
2428
2433
|
}
|
|
2434
|
+
// A step law holds from every state the laws allow, reached or not: from random such states (each scalar and cell
|
|
2435
|
+
// small, kept when every law and kind law holds there), a step that breaks it is a counterexample too, without a run.
|
|
2436
|
+
const steps1 = stepLaws.filter(sl => (sl.steps ?? 1) === 1), given = withKinds(r, laws);
|
|
2437
|
+
for (let trial = 0; steps1.length && trial < refuteBudget.trials * 4 && performance.now() < until; trial++) {
|
|
2438
|
+
const params = r.randomParams(rand, bound);
|
|
2439
|
+
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2440
|
+
continue;
|
|
2441
|
+
const st = { scalars: Object.fromEntries(Object.keys(m.state).map(n => [n, rand(8)])), maps: Object.fromEntries(Object.keys(m.maps ?? {}).map(n => [n, new Map(Array.from({ length: rand(3) }, () => [rand(bound + 1), rand(bound + 1)]))])) };
|
|
2442
|
+
if (m.history || !given.every(l => watch(m, run, l, params, bound).start(st)))
|
|
2443
|
+
continue;
|
|
2444
|
+
const input = r.randomInput(rand, bound, params);
|
|
2445
|
+
if (!allowed(m, run, params, st, input))
|
|
2446
|
+
continue;
|
|
2447
|
+
const next = run.step(params, st, input);
|
|
2448
|
+
for (const sl of steps1)
|
|
2449
|
+
if (!watch(m, run, sl, params, bound).step(st, input, next))
|
|
2450
|
+
return { goal: `${sl.name} @step law`, witness: { ...params, ...st.scalars, ...numbersOf(input) } };
|
|
2451
|
+
}
|
|
2429
2452
|
}
|
|
2430
2453
|
catch {
|
|
2431
2454
|
return undefined;
|
|
2432
2455
|
}
|
|
2433
2456
|
return undefined;
|
|
2434
2457
|
}
|
|
2435
|
-
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, run: out.run });
|
|
2458
|
+
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, ...(out.run ? { run: out.run } : {}) });
|
|
2436
2459
|
export function proveRule(r, laws) {
|
|
2437
2460
|
const refuted = refuteByRuns(r, laws);
|
|
2438
2461
|
if (refuted)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
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",
|