gdp-wired 0.10.1 → 0.12.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
@@ -51,7 +51,8 @@ All of these are proven automatically, and each proof is also a Lean theorem:
51
51
 
52
52
  - **Invariants**: a law that holds after every sequence of requests ("spend stays within the budget").
53
53
  - **Maps, sets and lists of names**: laws at every key ("no day passes its cap"), sets a request carries, conditions
54
- about every key, lists kept in order (`examples/sets.ts`, `examples/lists.ts`).
54
+ 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`).
55
56
  - **Step laws**: what one step does ("a balance only goes down", "a grant adds one"), and laws over several steps in a
56
57
  row.
57
58
  - **Histories**: sums and queries over a log of requests, like a sliding-window rate limit.
@@ -69,8 +70,9 @@ All of these are proven automatically, and each proof is also a Lean theorem:
69
70
  (`examples/datatypes.ts`).
70
71
  - **Lists a request carries, and trees**: an invoice's lines, with folds over them (the total, the taxable part) and
71
72
  lemmas about the folds proven by induction over the lines (`examples/invoices.ts`); a fold over a tree as a value (a
72
- spend roll-up over an org chart, sign-offs over a document's sections), its laws proven by induction over the tree
73
- (`examples/folds.ts`).
73
+ spend roll-up, sign-offs over a document's sections, an org chart where a manager has any number of reports), its laws
74
+ proven by induction over the tree (`examples/folds.ts`); a search tree, and a sheet of rows of cells, kept in a rule's
75
+ state as tables of nodes (`examples/trees.ts`).
74
76
  - **Pure functions and two-run laws**: laws about what a function returns, and laws comparing two calls, like "the
75
77
  secret never changes the answer". `examples/jev-fabric/` ports a Bend project's policy core and proves all 59 laws its
76
78
  Bend proofs state.
@@ -228,13 +230,24 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
228
230
  carried map of its running values whose equations every request is assumed to meet (the encoding computes them from the
229
231
  array, and a request that doesn't meet them isn't allowed), read as `Lines.fold.total`; `Lines.every`, `some`, `has`,
230
232
  `increasing(by)`, `at(i)`. A lemma about the folds (`({taxed, total}) => taxed.atMost(total)`) is proven by induction
231
- over the items when the rule is built, then assumed at every prefix (`examples/invoices.ts`).
233
+ over the items when the rule is built, then assumed at every prefix. A fold's start and step, and a lemma, may read the
234
+ params (`{from: ({fee}) => fee, step: (acc, line, {ceil}) => acc.plus(min(line.amount, ceil))}`); a request is then
235
+ encoded with them, `rule.encodeInput(values, params)` (`examples/invoices.ts`).
232
236
  - **Folds over trees** (`fold(name).params(...).inputs(...).leaf(env => ({...})).node((env, left, right) => ({...}))`,
233
237
  `proveFold`): a tree as a value (`{leaf: inputs}` or `{node: inputs, left, right}`), each leaf's values from its inputs
234
238
  and each node's from its own and its children's; a law about the values is proven by induction over the tree (the
235
239
  kernel's leaf and node goals, Lean's `fold_sound`), `f.run(tree, params)` evaluates the terms proven and `f.crossCheck`
236
- holds the builders to them (`examples/folds.ts`). A tree in a rule's state, and claims that need a lemma about the
237
- tree's shape nobody stated, stay Lean's.
240
+ holds the builders to them. Nodes with any number of children: `.children(from, (acc, kid) => ..., lemmas)` folds a
241
+ node's children into `kids`, then `.node((env, kids) => ...)`; it is the kernel's binary fold underneath (each child
242
+ list a chain of glue nodes), so Lean's `fold_sound` checks it unchanged, and `f.run` takes `{node, children: [...]}`
243
+ (`examples/folds.ts`).
244
+ - **Trees in a rule's state, and lists of lists** (`.nodes('Tree', record({key: nat}), ['left', 'right'], folds)`): a
245
+ table of nodes that only grows, node 0 the empty tree, `Tree.make(value, {left, right}, when)` making node size + 1 from
246
+ nodes already there; each fold (`{lo: {empty: 0, node: (x, kids) => when(kids.left.empty, x.key, kids.left.fold.lo)}}`)
247
+ kept with its node when it is made, and may read a table declared before (a row's total over its cells). A law about
248
+ a subtree is then a law about one node (`Tree.node(n).fold.lo`), proven like any law about every key; the table's own
249
+ laws (children earlier, folds coherent, nothing past the end) are proven beside it (`examples/trees.ts`: a search tree,
250
+ a sheet of rows of cells). Claims that need a lemma about the tree's shape nobody stated stay Lean's.
238
251
  - **State of any kind**: `.state({open: bool.from(true), status: oneOf('new', 'done').from('new'), owner: str().from(''), note: maybe(nat).from(null)})`;
239
252
  a step writes each kind's own values, `when(c, a, b)` chooses between any two values of a kind, and `rule.decode(state)`
240
253
  reads them back as runtime values.
@@ -249,7 +262,8 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
249
262
  - **Arithmetic** over the naturals: `plus`, `minus` (truncated), `times(k)`, `mul` (a product of two terms), `min`,
250
263
  `max`, `when(cond, a, b)`, `count(cond)`; conditions `lt`, `atMost`, `eq`, `and`, `or`, `not`, `implies`.
251
264
  - **Sets and quantifiers**: `.carries(...)` for sets a request brings, `everyKey(k => ...)` (and `has`, `subsetOf`,
252
- `disjointFrom`, `sameAs`) anywhere in a step or inside a law, `M.each(k => ...)` to write every key, and
265
+ `disjointFrom`, `sameAs`) anywhere in a step or inside a law, `everyKey((i, j) => ...)` in a step for every two keys
266
+ (each read on its own, compared with the other plainly: `i.lt(j)`), `M.each(k => ...)` to write every key, and
253
267
  `someKey(k => ..., witness?)` for "some key where", proven through a witness the prover finds or the one given.
254
268
  - **Functions** (`.fns({perMinute: divBy(60)})`, or `fn(arity, js, {facts, lean})` for your own): read like params that
255
269
  take arguments, `perMinute(t)`; the kernel knows each only by its facts (`.facts(...)`, and a library function's
@@ -329,7 +343,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
329
343
  | `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 |
330
344
  | `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
331
345
  | `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
332
- | `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), `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 16 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
346
+ | `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 16 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
333
347
  | `docs/` | The comparison with Bend and Lean, and the full scope |
334
348
  | `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
335
349
  | `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/`) |
@@ -37,7 +37,7 @@ function renameC(c, prefix, m, inputs) {
37
37
  if (c.op === 'and' || c.op === 'or')
38
38
  return { op: c.op, xs: c.xs.map(y => renameC(y, prefix, m, inputs)) };
39
39
  if (c.op === 'all')
40
- return { op: 'all', key: `${prefix}${c.key}`, c: renameC(c.c, prefix, m, inputs) }; // the key renamed with its side
40
+ return { ...c, key: `${prefix}${c.key}`, ...(c.keys ? { keys: c.keys.map(k => `${prefix}${k}`) } : {}), c: renameC(c.c, prefix, m, inputs) }; // the keys renamed with their side
41
41
  return { ...c, a: rename(c.a, prefix, m, inputs), b: rename(c.b, prefix, m, inputs) };
42
42
  }
43
43
  // A condition (or law) of one side, read in the composed machine.
@@ -253,7 +253,7 @@ export type MapWrite = {
253
253
  at: (key: Term) => TermLike;
254
254
  };
255
255
  };
256
- export declare const everyKey: (f: (key: Term) => Cond) => Cond;
256
+ export declare const everyKey: (f: (key: Term, key2: Term, key3: Term) => Cond) => Cond;
257
257
  export declare const someKey: (f: (key: Term) => Cond, witness?: TermLike) => Cond;
258
258
  export declare class MapRef {
259
259
  readonly name: string;
@@ -310,11 +310,11 @@ export type ItemsOp = {
310
310
  readonly parts: Record<string, ListOp>;
311
311
  readonly len: TermLike;
312
312
  };
313
- export type ListFold<V> = {
314
- from: number;
315
- step: (acc: Term, item: V) => TermLike;
313
+ export type ListFold<V, P = any> = {
314
+ from: number | ((p: P) => TermLike);
315
+ step: (acc: Term, item: V, p: P) => TermLike;
316
316
  };
317
- export type ListLemma = (acc: Record<string, Term>, i: Term) => Cond;
317
+ export type ListLemma<P = any> = (acc: Record<string, Term>, i: Term, p: P) => Cond;
318
318
  export declare class CarriedList<V, Fd extends string = never> {
319
319
  readonly name: string;
320
320
  readonly kind: Kind<V>;
@@ -322,8 +322,10 @@ export declare class CarriedList<V, Fd extends string = never> {
322
322
  readonly folds: Record<Fd, ListFold<V>>;
323
323
  private readonly map;
324
324
  readonly lemmas: Record<string, ListLemma>;
325
+ private readonly params?;
326
+ private readonly paramKinds;
325
327
  readonly fold: Record<Fd, Term>;
326
- constructor(name: string, kind: Kind<V>, len: Term, folds: Record<Fd, ListFold<V>>, map: (part: string) => MapRef, lemmas?: Record<string, ListLemma>);
328
+ constructor(name: string, kind: Kind<V>, len: Term, folds: Record<Fd, ListFold<V>>, map: (part: string) => MapRef, lemmas?: Record<string, ListLemma>, params?: Record<string, unknown> | undefined, paramKinds?: [string, Kind<unknown>][]);
327
329
  at(i: TermLike): V;
328
330
  after(fold: Fd, i: TermLike): Term;
329
331
  every(f: (item: V, i: Term) => Cond): Cond;
@@ -331,6 +333,8 @@ export declare class CarriedList<V, Fd extends string = never> {
331
333
  has(x: V): Cond;
332
334
  increasing(by: (item: V) => Term): Cond;
333
335
  equations(): Cond[];
336
+ private get readsParams();
337
+ private p;
334
338
  private running;
335
339
  prove(): Verdict;
336
340
  get maps(): string[];
@@ -358,6 +362,35 @@ export declare class ItemsRef<V> {
358
362
  within(): Forall;
359
363
  increasing(by: (x: V) => Term): Forall;
360
364
  }
365
+ export type NodeRef<V, C extends string, Fd extends string> = {
366
+ readonly index: Term;
367
+ readonly empty: Cond;
368
+ readonly value: V;
369
+ readonly fold: Record<Fd, Term>;
370
+ readonly kids: Record<C, Term>;
371
+ };
372
+ export type NodeFold<V, C extends string, Fd extends string = string, T = Record<string, NodeTable<any, any, any>>> = {
373
+ empty: number;
374
+ node: (value: V, kids: Record<C, NodeRef<V, C, Fd>>, tables: T) => TermLike;
375
+ };
376
+ type TablesOf<M> = {
377
+ [k in keyof M as M[k] extends NodeTable<any, any, any> ? k : never]: M[k];
378
+ };
379
+ export declare class NodeTable<V, C extends string = string, Fd extends string = string> {
380
+ readonly name: string;
381
+ readonly kind: Kind<V>;
382
+ readonly children: C[];
383
+ readonly folds: Record<Fd, NodeFold<V, C, Fd>>;
384
+ readonly size: Term;
385
+ private readonly map;
386
+ private readonly tables;
387
+ constructor(name: string, kind: Kind<V>, children: C[], folds: Record<Fd, NodeFold<V, C, Fd>>, size: Term, map: (part: string) => MapRef, tables?: Record<string, NodeTable<any, any, any>>);
388
+ node(n: TermLike): NodeRef<V, C, Fd>;
389
+ at(n: TermLike): V;
390
+ fits(kids: Record<C, TermLike>): Cond;
391
+ make(value: Writes<V>, kids: Record<C, TermLike>, when?: Cond): Record<string, TermLike | PartsWrite>;
392
+ laws(): [string, Forall][];
393
+ }
361
394
  export type Forall = {
362
395
  readonly forall: (key: Term, ...more: Term[]) => Cond;
363
396
  };
@@ -419,6 +452,7 @@ type Spec = {
419
452
  lists?: string[];
420
453
  cells?: [string, Kind<unknown>][];
421
454
  items?: [string, Kind<unknown>][];
455
+ nodes?: [string, Kind<unknown>, string[], Record<string, NodeFold<unknown, string>>][];
422
456
  kinded?: [string, Kind<unknown>][];
423
457
  carried?: string[];
424
458
  lists2?: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
@@ -467,6 +501,13 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
467
501
  }, M & {
468
502
  [k in keyof T]: ItemsRef<T[k] extends Kind<infer V> ? V : never>;
469
503
  }, F, R, Su, Q>;
504
+ nodes<const N extends string, V, const C extends string, Fd extends string = never>(name: N, of: Kind<V>, children: readonly C[], folds?: {
505
+ [f in Fd]: NodeFold<V, C, Fd, TablesOf<M>>;
506
+ }): Rule<P, I, S & {
507
+ [k in N as `${k}_size`]: Term;
508
+ }, M & {
509
+ [k in N]: NodeTable<V, C, Fd>;
510
+ }, F, R, Su, Q>;
470
511
  fns<T extends Record<string, FnDef>>(defs: T): Rule<P & {
471
512
  [k in keyof T]: (...args: TermLike[]) => Term;
472
513
  }, I, S, M, F, R, Su, Q>;
@@ -474,7 +515,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
474
515
  carries<const N extends string>(...names: N[]): Rule<P, I & {
475
516
  [k in N]: MapRef;
476
517
  }, S, M, F, R, Su, Q>;
477
- carriesList<const N extends string, V, Fd extends string = never>(name: N, of: Kind<V>, folds?: Record<Fd, ListFold<V>>, lemmas?: Record<string, (acc: Record<Fd, Term>, i: Term) => Cond>): Rule<P, I & {
518
+ carriesList<const N extends string, V, Fd extends string = never>(name: N, of: Kind<V>, folds?: Record<Fd, ListFold<V, P>>, lemmas?: Record<string, (acc: Record<Fd, Term>, i: Term, p: P) => Cond>): Rule<P, I & {
478
519
  [k in N]: CarriedList<V, Fd>;
479
520
  }, S, M, F, R, Su, Q>;
480
521
  flags<T extends Record<string, Cond>>(f: (env: P & I & S & M & F & Su & Q) => T): Rule<P, I, S, M, F & {
@@ -550,9 +591,12 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
550
591
  }): Record<string, number>;
551
592
  encodeInput(values: {
552
593
  [k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : I[k] extends CarriedList<any, any> ? readonly Raw[] : Raw;
594
+ }, params?: {
595
+ [k in keyof P]: Raw;
553
596
  }): Request;
554
597
  randomParams(rand: Rand, bound: number): Record<string, number>;
555
- randomInput(rand: Rand, bound: number): Request;
598
+ private paramValues;
599
+ randomInput(rand: Rand, bound: number, ps?: Record<string, number>): Request;
556
600
  decode(st: State): Record<string, Raw>;
557
601
  get orderedBy(): string | undefined;
558
602
  interpreter(): {
@@ -754,6 +798,15 @@ export declare class FoldRule<P extends Obj = {}, I extends Obj = {}, S extends
754
798
  node(f: (env: P & I, left: S, right: S) => {
755
799
  [k in keyof S]: TermLike;
756
800
  }): FoldRule<P, I, S>;
801
+ children<A extends Record<string, number | ((p: P) => TermLike)>>(from: A, step: (acc: {
802
+ [k in keyof A]: Term;
803
+ }, kid: S, p: P) => {
804
+ [k in keyof A]: TermLike;
805
+ }, lemmas?: Record<string, (acc: {
806
+ [k in keyof A]: Term;
807
+ }, p: P) => Cond>): RoseFold<P, I, S, {
808
+ [k in keyof A]: Term;
809
+ }>;
757
810
  private env;
758
811
  private names;
759
812
  private side;
@@ -774,4 +827,49 @@ export declare class FoldRule<P extends Obj = {}, I extends Obj = {}, S extends
774
827
  }): Agreement;
775
828
  }
776
829
  export declare const fold: (name: string) => FoldRule;
777
- export declare const proveFold: (f: FoldRule<Obj, Obj, Obj>, laws: Law[]) => Verdict;
830
+ export declare const proveFold: (f: {
831
+ fold: Fold;
832
+ lemmas?: Law[];
833
+ }, laws: Law[]) => Verdict;
834
+ export type RoseValue<I> = {
835
+ leaf: I;
836
+ } | {
837
+ node: I;
838
+ children: RoseValue<I>[];
839
+ };
840
+ type RoseSpec = {
841
+ from: Record<string, number | ((p: never) => TermLike)>;
842
+ step: (acc: never, kid: never, p: never) => Record<string, TermLike>;
843
+ lemmas: Record<string, (acc: never, p: never) => Cond>;
844
+ };
845
+ export declare class RoseFold<P extends Obj = {}, I extends Obj = {}, S extends Obj = {}, A extends Obj = {}> {
846
+ private readonly spec;
847
+ private readonly kids;
848
+ private readonly nodeOf?;
849
+ constructor(spec: FoldSpec, kids: RoseSpec, nodeOf?: ((env: never, kids: never) => Record<string, TermLike>) | undefined);
850
+ get name(): string;
851
+ node(f: (env: P & I, kids: A) => {
852
+ [k in keyof S]: TermLike;
853
+ }): RoseFold<P, I, S, A>;
854
+ private env;
855
+ private names;
856
+ private side;
857
+ private accs;
858
+ private start;
859
+ law(name: string, f: (env: P & S) => Cond): Law;
860
+ get lemmas(): Law[];
861
+ get fold(): Fold;
862
+ run(tree: RoseValue<{
863
+ [k in keyof I]: Raw;
864
+ }>, params?: {
865
+ [k in keyof P]?: Raw;
866
+ }): {
867
+ [k in keyof S]: number;
868
+ };
869
+ crossCheck(laws?: Law[], opts?: {
870
+ trials?: number;
871
+ depth?: number;
872
+ bound?: number;
873
+ rand?: Rand;
874
+ }): Agreement;
875
+ }