gdp-wired 0.12.4 → 0.12.6
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 +10 -4
- package/dist/core/dsl.d.ts +22 -25
- package/dist/core/dsl.js +71 -2
- package/dist/core/machine.js +12 -3
- package/dist/kernel/prove.js +11 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,6 +13,9 @@ the laws it must keep. gdp-wired proves them with no proof to write, shows a rea
|
|
|
13
13
|
npm install gdp-wired # or bun add gdp-wired; plain JavaScript with types, Node 20+, Bun or a browser
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
New to formal verification? [The guide](https://gdpwired.dev/guide) takes a rule from ordinary TypeScript to a proof in
|
|
17
|
+
seven steps, with recipes for limits, workflows, permissions and orders with line items.
|
|
18
|
+
|
|
16
19
|
```ts
|
|
17
20
|
import {min, nat, proveRule, rule, when} from 'gdp-wired'
|
|
18
21
|
|
|
@@ -52,7 +55,8 @@ All of these are proven automatically, and each proof is also a Lean theorem:
|
|
|
52
55
|
- **Invariants**: a law that holds after every sequence of requests ("spend stays within the budget").
|
|
53
56
|
- **Maps, sets and lists of names**: laws at every key ("no day passes its cap"), sets a request carries, conditions
|
|
54
57
|
about every key or every two keys ("a batch is taken only when its values increase"), lists kept in order
|
|
55
|
-
(`examples/sets.ts`, `examples/lists.ts`, `examples/pairs.ts`)
|
|
58
|
+
(`examples/sets.ts`, `examples/lists.ts`, `examples/pairs.ts`), and two groups of items with a set each, decided at
|
|
59
|
+
every pair at once (`examples/groups.ts`).
|
|
56
60
|
- **Step laws**: what one step does ("a balance only goes down", "a grant adds one"), and laws over up to 64 steps in
|
|
57
61
|
a row (`examples/steps.ts`).
|
|
58
62
|
- **Histories**: sums and queries over a log of requests, like a sliding-window rate limit.
|
|
@@ -234,7 +238,9 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
|
|
|
234
238
|
lemmas too, at the list's end and wherever it reads the list. A lemma reads the folds, the prefix's length and the
|
|
235
239
|
params, not the items: a fact about every item is a fold's (a count of the items that meet it). A fold's start and
|
|
236
240
|
step, and a lemma, may read the params (`{from: ({fee}) => fee, step: (acc, line, {ceil}) => acc.plus(min(line.amount, ceil))}`); a request is then
|
|
237
|
-
encoded with them, `rule.encodeInput(values, params)` (`examples/invoices.ts`).
|
|
241
|
+
encoded with them, `rule.encodeInput(values, params)` (`examples/invoices.ts`). `rule.foldsOf({Lines: [...]}, params)`
|
|
242
|
+
gives each fold at the list's end, computed the same way, without running the rule (to compare a decision's folds
|
|
243
|
+
with the code it describes).
|
|
238
244
|
- **Folds over trees** (`fold(name).params(...).inputs(...).leaf(env => ({...})).node((env, left, right) => ({...}))`,
|
|
239
245
|
`proveFold`): a tree as a value (`{leaf: inputs}` or `{node: inputs, left, right}`), each leaf's values from its inputs
|
|
240
246
|
and each node's from its own and its children's; a law about the values is proven by induction over the tree (the
|
|
@@ -349,7 +355,7 @@ Proving and checking: a law over 64 steps proves in about 5 s, and Lean checks i
|
|
|
349
355
|
| `core/` | Machines, the interpreter and JS compiler, the builders, composition (`compose.ts`), the linear solver and its checker, Houdini invariants, models, the first Lean export |
|
|
350
356
|
| `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
|
|
351
357
|
| `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
|
|
352
|
-
| `examples/` | The builders at work: `domains/` (187 rules across 21 domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `gdp.ts` (gdp-ts's example, its checks proven), `datatypes.ts` (records and variants), `lemmas.ts` (recurrences and their lemmas), `invoices.ts` (lists a request carries), `folds.ts` (folds over trees), `trees.ts` (trees and lists of lists in the state), `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 64 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
|
|
358
|
+
| `examples/` | The builders at work: `domains/` (187 rules across 21 domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `guide.ts` (the guide's code), `groups.ts` (two groups decided at every pair), `budget.ts`, `gdp.ts` (gdp-ts's example, its checks proven), `datatypes.ts` (records and variants), `lemmas.ts` (recurrences and their lemmas), `invoices.ts` (lists a request carries), `folds.ts` (folds over trees), `trees.ts` (trees and lists of lists in the state), `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 64 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
|
|
353
359
|
| `docs/` | The comparison with Bend and Lean, and the full scope |
|
|
354
360
|
| `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
|
|
355
361
|
| `research/` | Everything around the library ([`research/README.md`](research/README.md)): the GPU searches (`search/`, `hw/`, `modal-gpu/`), invariant and rank synthesis (`synth/`), relational laws and guard synthesis (`next/`), bit-vector laws (`sat/`), the enumeration and benchmarks (`enum/`, `gpu/`, `perf/`), the first Lean checker (`lean/`) and the first prototype, the audit path (`audit/`) |
|
|
@@ -377,7 +383,7 @@ bun test core/compile.test.ts core/runtime.test.ts kernel/history.test.ts
|
|
|
377
383
|
cd kernel && bun run-kernel.ts && bun test kernel.test.ts
|
|
378
384
|
```
|
|
379
385
|
|
|
380
|
-
The whole suite (
|
|
386
|
+
The whole suite (163 jobs, about 10 minutes of wall time and 46 minutes of job time) runs on [Modal](https://modal.com):
|
|
381
387
|
|
|
382
388
|
```sh
|
|
383
389
|
modal run modal/run.py --jobs modal/suite.json --out runs/modal/suite && sh modal/reap.sh
|
package/dist/core/dsl.d.ts
CHANGED
|
@@ -39,7 +39,8 @@ export declare function when(c: Cond, then: TermLike, otherwise: TermLike): Term
|
|
|
39
39
|
export declare function when(c: Cond, then: Decimal, otherwise: Decimal | number): Decimal;
|
|
40
40
|
export declare function when(c: Cond, then: Decimal | number, otherwise: Decimal): Decimal;
|
|
41
41
|
export declare function when(c: Cond, then: IntLike, otherwise: IntLike): Int;
|
|
42
|
-
export declare function when<K extends string>(c: Cond, then: Choice<K>, otherwise: Choice<K>): Choice<K>;
|
|
42
|
+
export declare function when<K extends string>(c: Cond, then: Choice<K> | NoInfer<K>, otherwise: Choice<K>): Choice<K>;
|
|
43
|
+
export declare function when<K extends string>(c: Cond, then: Choice<K>, otherwise: Choice<K> | NoInfer<K>): Choice<K>;
|
|
43
44
|
export declare function when(c: Cond, then: Str, otherwise: Str): Str;
|
|
44
45
|
export declare function when<V>(c: Cond, then: Maybe<V>, otherwise: Maybe<V>): Maybe<V>;
|
|
45
46
|
export declare function when(c: Cond, then: Cond, otherwise: Cond): Cond;
|
|
@@ -129,6 +130,8 @@ export declare class Choice<K extends string> {
|
|
|
129
130
|
readonly values: readonly K[];
|
|
130
131
|
constructor(n: Term, values: readonly K[]);
|
|
131
132
|
is(k: K): Cond;
|
|
133
|
+
of(k: K): Choice<K>;
|
|
134
|
+
private index;
|
|
132
135
|
match(cases: {
|
|
133
136
|
[k in K]: () => Cond;
|
|
134
137
|
}): Cond;
|
|
@@ -574,20 +577,10 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
574
577
|
private verdict;
|
|
575
578
|
private crossStep;
|
|
576
579
|
compare<St extends Record<string, Raw>>(reference: {
|
|
577
|
-
init
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
[k in keyof P]: Raw;
|
|
582
|
-
}, st: St, request: {
|
|
583
|
-
[k in keyof I]: Raw;
|
|
584
|
-
}) => St;
|
|
585
|
-
}, opts?: Draws<{
|
|
586
|
-
[k in keyof I]: Raw;
|
|
587
|
-
}> & {
|
|
588
|
-
params?: (rand: Rand) => {
|
|
589
|
-
[k in keyof P]: Raw;
|
|
590
|
-
};
|
|
580
|
+
init(params: Value<P>): St;
|
|
581
|
+
step(params: Value<P>, st: St, request: RawInputs<I>): St;
|
|
582
|
+
}, opts?: Draws<RawInputs<I>> & {
|
|
583
|
+
params?: (rand: Rand) => Value<P>;
|
|
591
584
|
depth?: number;
|
|
592
585
|
}): Agreement;
|
|
593
586
|
encodeParams(values: {
|
|
@@ -598,6 +591,11 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
598
591
|
}, params?: {
|
|
599
592
|
[k in keyof P]: Raw;
|
|
600
593
|
}): Request;
|
|
594
|
+
foldsOf(values: {
|
|
595
|
+
[k in keyof I]?: I[k] extends CarriedList<any, any> ? readonly Raw[] : never;
|
|
596
|
+
}, params?: {
|
|
597
|
+
[k in keyof P]: Raw;
|
|
598
|
+
}): Record<string, Record<string, number>>;
|
|
601
599
|
randomParams(rand: Rand, bound: number): Record<string, number>;
|
|
602
600
|
private paramValues;
|
|
603
601
|
randomInput(rand: Rand, bound: number, ps?: Record<string, number>): Request;
|
|
@@ -657,6 +655,9 @@ type CaseRaw<C extends Cases, T extends string = 'tag'> = {
|
|
|
657
655
|
type Value<T> = {
|
|
658
656
|
[k in keyof T]: ValueOf<T[k]>;
|
|
659
657
|
};
|
|
658
|
+
type RawInputs<T> = {
|
|
659
|
+
[k in keyof T]: T[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : T[k] extends CarriedList<infer V, infer _F> ? readonly ValueOf<V>[] : ValueOf<T[k]>;
|
|
660
|
+
};
|
|
660
661
|
type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any, any> | Maybe<any> | {
|
|
661
662
|
[f: string]: Field | number;
|
|
662
663
|
};
|
|
@@ -677,16 +678,8 @@ export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj =
|
|
|
677
678
|
private result;
|
|
678
679
|
law(name: string, given: (env: P & I, out: O) => Cond | Cond[], then: (out: O, env: P & I) => Cond): Decision;
|
|
679
680
|
relate(name: string, given: (a: P & I, b: P & I) => Cond | Cond[], then: (outA: O, outB: O, a: P & I, b: P & I) => Cond): Decision;
|
|
680
|
-
compare(reference: (values: {
|
|
681
|
-
|
|
682
|
-
}, params: {
|
|
683
|
-
[k in keyof P]: Raw;
|
|
684
|
-
}) => Partial<Value<O>>, opts?: Draws<{
|
|
685
|
-
[k in keyof I]: Raw;
|
|
686
|
-
}> & {
|
|
687
|
-
params?: {
|
|
688
|
-
[k in keyof P]: Raw;
|
|
689
|
-
};
|
|
681
|
+
compare(reference: (values: Value<I>, params: Value<P>) => Partial<Value<O>>, opts?: Draws<Value<I>> & {
|
|
682
|
+
params?: Value<P>;
|
|
690
683
|
}): Agreement;
|
|
691
684
|
get rule(): Rule<P, I>;
|
|
692
685
|
private compiledRule?;
|
|
@@ -751,6 +744,10 @@ export type Verdict = {
|
|
|
751
744
|
witness?: Record<string, number>;
|
|
752
745
|
why?: string;
|
|
753
746
|
trees?: Record<string, Tree>;
|
|
747
|
+
run?: {
|
|
748
|
+
params: Record<string, number>;
|
|
749
|
+
trace: Record<string, unknown>[];
|
|
750
|
+
};
|
|
754
751
|
};
|
|
755
752
|
export declare class Unproven extends Error {
|
|
756
753
|
readonly verdict: Verdict;
|
package/dist/core/dsl.js
CHANGED
|
@@ -94,6 +94,11 @@ export function when(c, then, otherwise) {
|
|
|
94
94
|
throw new Error('when: records of other fields');
|
|
95
95
|
return Object.fromEntries(Object.keys(then).map(f => [f, when(c, then[f], otherwise[f])]));
|
|
96
96
|
}
|
|
97
|
+
// A choice's name beside a choice is that choice's value: when(closing, 'closed', status).
|
|
98
|
+
if (then instanceof Choice && typeof otherwise === 'string')
|
|
99
|
+
return when(c, then, then.of(otherwise));
|
|
100
|
+
if (typeof then === 'string' && otherwise instanceof Choice)
|
|
101
|
+
return when(c, otherwise.of(then), otherwise);
|
|
97
102
|
if (then instanceof Choice && otherwise instanceof Choice)
|
|
98
103
|
return new Choice(when(c, then.n, otherwise.n), then.values);
|
|
99
104
|
if (then instanceof Str && otherwise instanceof Str)
|
|
@@ -348,11 +353,14 @@ export class Choice {
|
|
|
348
353
|
this.n = n;
|
|
349
354
|
this.values = values;
|
|
350
355
|
}
|
|
351
|
-
is(k) {
|
|
356
|
+
is(k) { return this.n.eq(this.index(k)); }
|
|
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.index(k)), this.values); }
|
|
359
|
+
index(k) {
|
|
352
360
|
const i = this.values.indexOf(k);
|
|
353
361
|
if (i < 0)
|
|
354
362
|
throw new Error(`${k} is not one of ${this.values.join(', ')}`);
|
|
355
|
-
return
|
|
363
|
+
return i;
|
|
356
364
|
}
|
|
357
365
|
match(cases) {
|
|
358
366
|
const results = this.values.map(k => [k, cases[k]()]);
|
|
@@ -2160,6 +2168,16 @@ export class Rule {
|
|
|
2160
2168
|
}
|
|
2161
2169
|
return out;
|
|
2162
2170
|
}
|
|
2171
|
+
// Each carried list's folds at its end (what `L.fold.<f>` reads), computed from its items as encodeInput computes
|
|
2172
|
+
// them, without running the rule: `foldsOf({Lines: [...]})` → {Lines: {total: 30}}. Only the lists given.
|
|
2173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2174
|
+
foldsOf(values, params) {
|
|
2175
|
+
const at = params && this.paramValues(this.encodeParams(params));
|
|
2176
|
+
return Object.fromEntries((this.spec.lists2 ?? []).filter(([n]) => n in values).map(([n, k, fs]) => {
|
|
2177
|
+
const { maps, len } = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, at).encode(values[n]);
|
|
2178
|
+
return [n, Object.fromEntries(Object.keys(fs).map(f => [f, maps[`${n}$${f}`].get(len) ?? 0]))];
|
|
2179
|
+
}));
|
|
2180
|
+
}
|
|
2163
2181
|
// Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
|
|
2164
2182
|
randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
|
|
2165
2183
|
// The params' values by their kinds, as constants (what a fold that reads them is computed with).
|
|
@@ -2367,11 +2385,62 @@ const goalsVerdict = (goals) => {
|
|
|
2367
2385
|
};
|
|
2368
2386
|
// The kinds' laws first, those the caller didn't pass already (by name).
|
|
2369
2387
|
const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.name === k.name)), ...laws];
|
|
2388
|
+
// A real run from the start that breaks a law or a step law, from a few random runs (each request drawn by its kinds,
|
|
2389
|
+
// those the rule's assumption refuses skipped): a false law is refused at once, with the run, where the symbolic search
|
|
2390
|
+
// would have to decide every case of its goal to reach one. Nothing here is trusted; a run it finds is a run the
|
|
2391
|
+
// interpreter took. A rule that reads functions is skipped: the kernel knows them only by their facts (a recurrence's
|
|
2392
|
+
// induction machine reads the function it proves things about), so a run computing them proves nothing about the goal.
|
|
2393
|
+
const refuteBudget = { trials: 60, depth: 6, bound: 3, ms: 150 };
|
|
2394
|
+
function refuteByRuns(r, laws, stepLaws = []) {
|
|
2395
|
+
const m = r.machine;
|
|
2396
|
+
if (Object.keys(m.fns ?? {}).length || m.facts?.length)
|
|
2397
|
+
return undefined;
|
|
2398
|
+
const run = interpret(m), rand = seeded(13), until = performance.now() + refuteBudget.ms, bound = refuteBudget.bound;
|
|
2399
|
+
const shown = (input) => Object.fromEntries(Object.entries(input).map(([n, x]) => [n, typeof x === 'number' ? x : Object.fromEntries(x)]));
|
|
2400
|
+
try {
|
|
2401
|
+
for (let trial = 0; trial < refuteBudget.trials && performance.now() < until; trial++) {
|
|
2402
|
+
const params = r.randomParams(rand, bound);
|
|
2403
|
+
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2404
|
+
continue;
|
|
2405
|
+
const watching = [...laws, ...stepLaws].map(w => ({ w, on: watch(m, run, w, params, bound) }));
|
|
2406
|
+
let st = run.init(params);
|
|
2407
|
+
const trace = [];
|
|
2408
|
+
// The witness: the params, the state the breaking step starts from (or the start) and its request's own values.
|
|
2409
|
+
const broken = (w, at, before, input) => ({ goal: `${w.name} ${at}`, witness: { ...params, ...before.scalars, ...(input ? numbersOf(input) : {}) }, run: { params, trace: trace.map(shown) } });
|
|
2410
|
+
for (const { w, on } of watching)
|
|
2411
|
+
if (!('then' in w) && !on.start(st))
|
|
2412
|
+
return broken(w, '@start', st);
|
|
2413
|
+
for (let d = 0, tries = 0; d < refuteBudget.depth && tries < 40; tries++) {
|
|
2414
|
+
const input = r.randomInput(rand, bound, params);
|
|
2415
|
+
if (!allowed(m, run, params, st, input))
|
|
2416
|
+
continue;
|
|
2417
|
+
d++;
|
|
2418
|
+
const next = run.step(params, st, input);
|
|
2419
|
+
trace.push(input);
|
|
2420
|
+
for (const { w, on } of watching)
|
|
2421
|
+
if (!on.step(st, input, next))
|
|
2422
|
+
return broken(w, !('then' in w) ? '@step' : (w.steps ?? 1) > 1 ? `@${w.steps} steps` : '@step law', st, input);
|
|
2423
|
+
st = next;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
catch {
|
|
2428
|
+
return undefined;
|
|
2429
|
+
}
|
|
2430
|
+
return undefined;
|
|
2431
|
+
}
|
|
2432
|
+
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, run: out.run });
|
|
2370
2433
|
export function proveRule(r, laws) {
|
|
2434
|
+
const refuted = refuteByRuns(r, laws);
|
|
2435
|
+
if (refuted)
|
|
2436
|
+
return refutedVerdict(refuted);
|
|
2371
2437
|
const out = proveLaws(r.machine, withKinds(r, laws));
|
|
2372
2438
|
return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
2373
2439
|
}
|
|
2374
2440
|
export function proveSteps(r, laws, stepLaws) {
|
|
2441
|
+
const refuted = refuteByRuns(r, laws, stepLaws);
|
|
2442
|
+
if (refuted)
|
|
2443
|
+
return refutedVerdict(refuted);
|
|
2375
2444
|
const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
|
|
2376
2445
|
return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
2377
2446
|
}
|
package/dist/core/machine.js
CHANGED
|
@@ -727,15 +727,24 @@ function renameKey(lin, from, to) {
|
|
|
727
727
|
}
|
|
728
728
|
// --- real counterexamples ----------------------------------------------------------------------------------------------
|
|
729
729
|
// Every sequence of up to `depth` requests, with params and inputs within 0..bound, from the start: the first that breaks
|
|
730
|
-
// the law, checked with the interpreter. A
|
|
731
|
-
//
|
|
730
|
+
// the law, checked with the interpreter. A variable `pre` or `assume` bounds by a constant of at most 15 (a choice, a
|
|
731
|
+
// yes/no, a small upTo) takes every value it may instead. A law that fails here is false; one that only fails `prove` may
|
|
732
|
+
// just need a stronger invariant.
|
|
732
733
|
export function search(m, law, depth = 3, bound = 2) {
|
|
733
734
|
const run = interpret(m);
|
|
734
735
|
const holds = (params, st) => {
|
|
735
736
|
const keys = law.forall ? [...new Set([0, ...Object.values(st.maps).flatMap(cells => [...cells.keys()])])] : [0];
|
|
736
737
|
return keys.every(key => evalC(law.holds, { ...params, ...(law.forall ? { [law.forall]: key } : {}) }, st, m));
|
|
737
738
|
};
|
|
738
|
-
const
|
|
739
|
+
const tops = new Map();
|
|
740
|
+
const conj = (c) => (c === undefined ? [] : c.op === 'and' ? c.xs.flatMap(conj) : [c]);
|
|
741
|
+
for (const c of [...conj(m.pre), ...conj(m.assume)])
|
|
742
|
+
if ((c.op === '<' || c.op === '<=') && c.a.op === 'var' && c.b.op === 'const') {
|
|
743
|
+
const top = c.op === '<' ? c.b.value - 1 : c.b.value;
|
|
744
|
+
if (top >= 0 && top <= 15)
|
|
745
|
+
tops.set(c.a.name, Math.min(top, tops.get(c.a.name) ?? top));
|
|
746
|
+
}
|
|
747
|
+
const tuples = (names) => names.reduce((acc, name) => acc.flatMap(t => Array.from({ length: (tops.get(name) ?? bound) + 1 }, (_, i) => ({ ...t, [name]: i }))), [{}]);
|
|
739
748
|
const assumed = (env, st) => m.assume === undefined || evalC(m.assume, env, st, m);
|
|
740
749
|
const meetsPre = (params) => m.pre === undefined || evalC(m.pre, params, { scalars: {}, maps: {} }, m);
|
|
741
750
|
for (const params of tuples(m.params)) {
|
package/dist/kernel/prove.js
CHANGED
|
@@ -137,9 +137,16 @@ const solveP = (rows, maxRows) => {
|
|
|
137
137
|
s.rows += rows.length;
|
|
138
138
|
return out;
|
|
139
139
|
};
|
|
140
|
+
// PROVE_TRACE=1 logs each goal grown from its root (hypotheses, nodes, time, outcome) to stderr.
|
|
141
|
+
const proveTrace = !!globalThis.process?.env?.PROVE_TRACE;
|
|
140
142
|
// The budget: 200,000 nodes and, by default, a second per hypothesis (at least 20 seconds) per goal; past either the goal
|
|
141
143
|
// is left `unknown`. A goal with many instances (a law about two keys at its pairs) has that many more rows per leaf.
|
|
142
144
|
export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
|
|
145
|
+
if (proveTrace && !path.size && !budget.nodes) {
|
|
146
|
+
const t = performance.now(), out = growRoot(goal, path, budget);
|
|
147
|
+
console.error(`[grow] ${goal.name}: ${goal.hyps.length} hyps, ${budget.nodes} nodes, ${Math.round(performance.now() - t)} ms, ${'tree' in out ? 'proven' : `${out.stuck}${out.why ? ` (${out.why})` : ''}`}`);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
143
150
|
if (!profile.on || path.size || budget.nodes)
|
|
144
151
|
return growRoot(goal, path, budget);
|
|
145
152
|
const t = performance.now(), s0 = profile.solves, m0 = profile.solveMs, r0 = profile.rowsMs, n0 = profile.nodes;
|
|
@@ -667,14 +674,15 @@ const flagInstances = (m, keys, focus = []) => Object.fromEntries(Object.entries
|
|
|
667
674
|
}));
|
|
668
675
|
// A k-step law whose steps keep a condition (a flag that holds at one step and is read again at the next) fails at a
|
|
669
676
|
// later step's witness only where the condition held before: so, per quantified flag over one key, the instances at the
|
|
670
|
-
// law's own keys and that flag's witness at every step (a later step's witness is a key an earlier frame may
|
|
671
|
-
// flag holds at every key: kernel.ts's checkStepLaws). Few instances, where the shared ones are many and
|
|
677
|
+
// law's own keys and that flag's witness at every step (a later step's input or witness is a key an earlier frame may
|
|
678
|
+
// name, as a flag holds at every key: kernel.ts's checkStepLaws). Few instances, where the shared ones are many and
|
|
679
|
+
// mostly idle.
|
|
672
680
|
const keptAcross = (m, laws, inst, own, k) => k < 2 ? [] : Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => {
|
|
673
681
|
if (c.op !== 'all' || c.keys?.length)
|
|
674
682
|
return [];
|
|
675
683
|
const ws = Array.from({ length: k }, (_, i) => ({ op: 'var', name: i ? `w?${f}@${i + 1}` : `w?${f}` })), keys = [...own, ...ws.slice(0, 1)];
|
|
676
684
|
const keep = new Set(keys.map(e => JSON.stringify(e)));
|
|
677
|
-
return [Object.fromEntries(Object.entries(inst).map(([n, ks]) => [n,
|
|
685
|
+
return [Object.fromEntries(Object.entries(inst).map(([n, ks]) => [n, n === `?${f}` ? [...keys, ...ws.slice(1)].filter((e, i, all) => all.findIndex(o => JSON.stringify(o) === JSON.stringify(e)) === i) : arity(m, laws, n) === 1 ? ks.filter(e => keep.has(JSON.stringify(e))) : ks]))];
|
|
678
686
|
});
|
|
679
687
|
const pairFlags = (m) => Object.values(m.step.flags ?? {}).some(c => c.op === 'all' && !!c.keys?.length);
|
|
680
688
|
// How many keys each instance of a name takes: a law's, or a quantified flag's (?f).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
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",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"build": "bun scripts/build.ts",
|
|
90
90
|
"prepublishOnly": "bun scripts/build.ts",
|
|
91
91
|
"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 && 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/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/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"
|
|
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 && 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/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
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@gdp-ts/core": "0.1.0",
|