gdp-wired 0.10.1 → 0.11.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
@@ -69,8 +69,9 @@ All of these are proven automatically, and each proof is also a Lean theorem:
69
69
  (`examples/datatypes.ts`).
70
70
  - **Lists a request carries, and trees**: an invoice's lines, with folds over them (the total, the taxable part) and
71
71
  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`).
72
+ spend roll-up, sign-offs over a document's sections, an org chart where a manager has any number of reports), its laws
73
+ proven by induction over the tree (`examples/folds.ts`); a search tree, and a sheet of rows of cells, kept in a rule's
74
+ state as tables of nodes (`examples/trees.ts`).
74
75
  - **Pure functions and two-run laws**: laws about what a function returns, and laws comparing two calls, like "the
75
76
  secret never changes the answer". `examples/jev-fabric/` ports a Bend project's policy core and proves all 59 laws its
76
77
  Bend proofs state.
@@ -228,13 +229,24 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
228
229
  carried map of its running values whose equations every request is assumed to meet (the encoding computes them from the
229
230
  array, and a request that doesn't meet them isn't allowed), read as `Lines.fold.total`; `Lines.every`, `some`, `has`,
230
231
  `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`).
232
+ 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
233
+ params (`{from: ({fee}) => fee, step: (acc, line, {ceil}) => acc.plus(min(line.amount, ceil))}`); a request is then
234
+ encoded with them, `rule.encodeInput(values, params)` (`examples/invoices.ts`).
232
235
  - **Folds over trees** (`fold(name).params(...).inputs(...).leaf(env => ({...})).node((env, left, right) => ({...}))`,
233
236
  `proveFold`): a tree as a value (`{leaf: inputs}` or `{node: inputs, left, right}`), each leaf's values from its inputs
234
237
  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
238
  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.
239
+ holds the builders to them. Nodes with any number of children: `.children(from, (acc, kid) => ..., lemmas)` folds a
240
+ node's children into `kids`, then `.node((env, kids) => ...)`; it is the kernel's binary fold underneath (each child
241
+ list a chain of glue nodes), so Lean's `fold_sound` checks it unchanged, and `f.run` takes `{node, children: [...]}`
242
+ (`examples/folds.ts`).
243
+ - **Trees in a rule's state, and lists of lists** (`.nodes('Tree', record({key: nat}), ['left', 'right'], folds)`): a
244
+ table of nodes that only grows, node 0 the empty tree, `Tree.make(value, {left, right}, when)` making node size + 1 from
245
+ nodes already there; each fold (`{lo: {empty: 0, node: (x, kids) => when(kids.left.empty, x.key, kids.left.fold.lo)}}`)
246
+ 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
247
+ 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
248
+ laws (children earlier, folds coherent, nothing past the end) are proven beside it (`examples/trees.ts`: a search tree,
249
+ a sheet of rows of cells). Claims that need a lemma about the tree's shape nobody stated stay Lean's.
238
250
  - **State of any kind**: `.state({open: bool.from(true), status: oneOf('new', 'done').from('new'), owner: str().from(''), note: maybe(nat).from(null)})`;
239
251
  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
252
  reads them back as runtime values.
@@ -329,7 +341,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
329
341
  | `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
342
  | `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
331
343
  | `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) |
344
+ | `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
345
  | `docs/` | The comparison with Bend and Lean, and the full scope |
334
346
  | `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
335
347
  | `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/`) |
@@ -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
+ }
package/dist/core/dsl.js CHANGED
@@ -15,7 +15,7 @@
15
15
  //
16
16
  // A step returns its updates as one object: every right-hand side reads the state before the step, as the kernel and
17
17
  // both runtimes do.
18
- import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf } from "./machine.js";
18
+ import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf, pairedArgs } from "./machine.js";
19
19
  import { allowed, keysOf, requestEnv, watch } from "./watch.js";
20
20
  import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
21
21
  import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
@@ -867,6 +867,7 @@ export class CellsRef {
867
867
  return { parts: Object.fromEntries(this.kind.vars(this.name).map(part => [part, this.map(part).each(k => partsOf(this.kind, this.name, f(k))[part])])) };
868
868
  }
869
869
  }
870
+ const fromOf = (d, p) => (typeof d.from === 'number' ? d.from : d.from(p));
870
871
  export class CarriedList {
871
872
  name;
872
873
  kind;
@@ -874,14 +875,20 @@ export class CarriedList {
874
875
  folds;
875
876
  map;
876
877
  lemmas;
878
+ params;
879
+ paramKinds;
877
880
  fold;
878
- constructor(name, kind, len, folds, map, lemmas = {}) {
881
+ // `params`: the params' values the folds read (by their kinds: terms for the kernel, constants to encode a request),
882
+ // undefined where none are given; `paramKinds`, the params themselves, for the lemmas' induction.
883
+ constructor(name, kind, len, folds, map, lemmas = {}, params, paramKinds = []) {
879
884
  this.name = name;
880
885
  this.kind = kind;
881
886
  this.len = len;
882
887
  this.folds = folds;
883
888
  this.map = map;
884
889
  this.lemmas = lemmas;
890
+ this.params = params;
891
+ this.paramKinds = paramKinds;
885
892
  this.fold = Object.fromEntries(Object.keys(folds).map(f => [f, map(`${name}$${f}`).at(len)]));
886
893
  }
887
894
  at(i) { return this.kind.build(this.name, part => this.map(part).at(i)); }
@@ -897,12 +904,21 @@ export class CarriedList {
897
904
  equations() {
898
905
  const folds = Object.entries(this.folds), lemmas = Object.values(this.lemmas);
899
906
  const at = (k) => {
900
- const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x)));
907
+ const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x, this.p())));
901
908
  const below = [...this.kind.invariant(x), ...steps];
902
- return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k))))] : []));
909
+ return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k, this.p()))))] : []));
903
910
  };
904
911
  const any = this.kind.invariant(this.kind.value(this.name)).length + folds.length + lemmas.length > 0;
905
- return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(d.from)), ...(any ? [everyKey(at)] : [])];
912
+ return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(fromOf(d, this.p()))), ...(any ? [everyKey(at)] : [])];
913
+ }
914
+ // Whether a fold reads the params (its start a function, or its step taking them).
915
+ get readsParams() { return Object.values(this.folds).some(d => typeof d.from === 'function' || d.step.length > 2); }
916
+ p() {
917
+ if (this.params)
918
+ return this.params;
919
+ if (this.readsParams)
920
+ throw new RangeError(`${this.name}'s folds read the params: give them (encodeInput(values, params))`);
921
+ return {};
906
922
  }
907
923
  running(k) { return Object.fromEntries(Object.keys(this.folds).map(f => [f, this.after(f, k)])); }
908
924
  // The lemmas, proven: the folds as a machine counting i up from 0 over items that are functions of i known only by
@@ -916,10 +932,11 @@ export class CarriedList {
916
932
  const kindFact = this.kind.invariant(this.kind.value(this.name)).length ? { 'every item is a value of its kind': forall(k => all(...this.kind.invariant(item(k)))) } : {};
917
933
  const fns = Object.fromEntries(parts.map((p, j) => [fname(p), fn(1, 'x => 0', j === 0 ? { facts: () => kindFact } : {})]));
918
934
  const folds = Object.entries(this.folds);
919
- const r = rule(`${this.name}'s folds, by induction`).fns(fns).inputs({}).state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.from])) })
920
- .step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i))])) }));
935
+ const r = rule(`${this.name}'s folds, by induction`).params(Object.fromEntries(this.paramKinds)).fns(fns).inputs({})
936
+ .state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, typeof d.from === 'number' ? d.from : (p) => d.from(p)])) })
937
+ .step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i), env)])) }));
921
938
  const accs = (env) => Object.fromEntries(folds.map(([f]) => [f, env[`acc$${f}`]]));
922
- return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i))));
939
+ return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))));
923
940
  }
924
941
  // Every carried map it is kept in: the items' parts, then each fold's.
925
942
  get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
@@ -933,13 +950,13 @@ export class CarriedList {
933
950
  if (x !== 0)
934
951
  maps[part].set(i, x); });
935
952
  for (const [f, d] of Object.entries(this.folds)) {
936
- const acc = maps[`${this.name}$${f}`];
937
- let value = d.from;
953
+ const acc = maps[`${this.name}$${f}`], p = this.p();
954
+ let value = valOf(fromOf(d, p));
938
955
  if (value !== 0)
939
956
  acc.set(0, value);
940
957
  items.forEach((enc, i) => {
941
958
  const item = this.kind.build(this.name, part => new Term({ op: 'const', value: enc[part] }, enc[part]));
942
- const next = valOf(d.step(new Term({ op: 'const', value }, value), item));
959
+ const next = valOf(d.step(new Term({ op: 'const', value }, value), item, p));
943
960
  if (next === undefined || !Number.isSafeInteger(next) || next < 0)
944
961
  throw new RangeError(`${this.name}'s fold ${f} reads more than its items`);
945
962
  value = next;
@@ -981,6 +998,66 @@ export class ItemsRef {
981
998
  within() { return forall(k => this.len.atMost(k).implies(all(...this.names.map(part => this.list(part).at(k).eq(0))))); }
982
999
  increasing(by) { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(by(this.at(i)).lt(by(this.at(j))))); }
983
1000
  }
1001
+ export class NodeTable {
1002
+ name;
1003
+ kind;
1004
+ children;
1005
+ folds;
1006
+ size;
1007
+ map;
1008
+ tables;
1009
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1010
+ constructor(name, kind, children, folds, size, map, tables = {}) {
1011
+ this.name = name;
1012
+ this.kind = kind;
1013
+ this.children = children;
1014
+ this.folds = folds;
1015
+ this.size = size;
1016
+ this.map = map;
1017
+ this.tables = tables;
1018
+ }
1019
+ // The node at n (0, or past the end: the empty tree).
1020
+ node(n) {
1021
+ const index = termAt(n);
1022
+ return { index, empty: index.eq(0), value: this.kind.build(this.name, part => this.map(part).at(index)),
1023
+ fold: Object.fromEntries(Object.keys(this.folds).map(f => [f, this.map(`${this.name}$${f}`).at(index)])),
1024
+ kids: Object.fromEntries(this.children.map(c => [c, this.map(`${this.name}^${c}`).at(index)])) };
1025
+ }
1026
+ at(n) { return this.node(n).value; }
1027
+ // Whether nodes can be made from these children (each 0 or a node already there).
1028
+ fits(kids) { return all(...this.children.map(c => termAt(kids[c]).atMost(this.size))); }
1029
+ // Node size + 1, from a value and children already there, when `when` holds (and they fit): its value, children and
1030
+ // folds written, the size grown.
1031
+ make(value, kids, when = yes) {
1032
+ const n = this.size.plus(1), go = when.and(this.fits(kids)), parts = partsOf(this.kind, this.name, value);
1033
+ const val = this.kind.build(this.name, part => termAt(parts[part]));
1034
+ const kidRefs = Object.fromEntries(this.children.map(c => [c, this.node(termAt(kids[c]))]));
1035
+ const writes = {};
1036
+ const put = (part, x) => { writes[part] = this.map(part).set(n, when_(go, x, this.map(part).at(n))); };
1037
+ for (const part of this.kind.vars(this.name))
1038
+ put(part, parts[part]);
1039
+ for (const c of this.children)
1040
+ put(`${this.name}^${c}`, kids[c]);
1041
+ for (const [f, d] of Object.entries(this.folds))
1042
+ put(`${this.name}$${f}`, d.node(val, kidRefs, this.tables));
1043
+ return { [this.name]: { parts: writes }, [`${this.name}_size`]: when_(go, n, this.size) };
1044
+ }
1045
+ // The table's own laws: every node's children are earlier nodes and its folds are its children's, and node 0 and every
1046
+ // key past the end hold nothing (each fold's empty).
1047
+ laws() {
1048
+ const inside = (k) => k.atLeast(1).and(k.atMost(this.size));
1049
+ const folds = Object.entries(this.folds);
1050
+ return [
1051
+ ...(this.children.length ? [[`every ${this.name} node's children are earlier nodes`, forall(k => inside(k).implies(all(...this.children.map(c => this.node(k).kids[c].lt(k)))))]] : []),
1052
+ ...(folds.length ? [[`every ${this.name} node's folds are its children's`, forall(k => inside(k).implies(all(...folds.map(([f, d]) => {
1053
+ const x = this.node(k);
1054
+ return x.fold[f].eq(d.node(x.value, Object.fromEntries(this.children.map(c => [c, this.node(x.kids[c])])), this.tables));
1055
+ }))))]] : []),
1056
+ [`nothing in ${this.name} past its end`, forall(k => k.eq(0).or(this.size.lt(k)).implies(all(...this.kind.vars(this.name).map(part => this.map(part).at(k).eq(0)), ...this.children.map(c => this.node(k).kids[c].eq(0)), ...folds.map(([f, d]) => this.node(k).fold[f].eq(d.empty)))))],
1057
+ ];
1058
+ }
1059
+ }
1060
+ const termAt = (t) => (t instanceof Term ? t : lit(t));
984
1061
  // A law about every key of a map: proven at a fresh key, assumed at the keys a step reads.
985
1062
  // With two parameters or more, about every two keys or more: forall((i, j) => i.lt(j).implies(...)).
986
1063
  export function forall(f) { return { forall: f }; }
@@ -1194,6 +1271,8 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1194
1271
  if (!plain.test(n) || reservedInRecur.includes(n) || n === name)
1195
1272
  throw new Error(`recur ${name}: ${n} can't name an argument or a function here (${reservedInRecur.join(', ')} and ${name} are taken)`);
1196
1273
  distinctNames(`recur ${name}`, [...args, ...uses.map(([u]) => u)]);
1274
+ // A rule reads the function at its arguments paired, which the kernel multiplies out: with five further arguments that
1275
+ // is a polynomial of degree 32, past what it can hold.
1197
1276
  if (args.length > 4)
1198
1277
  throw new Error(`recur ${name}: at most four further arguments`);
1199
1278
  const named = (n) => new Term({ op: 'var', name: n });
@@ -1244,8 +1323,9 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1244
1323
  const keysOf = (n) => (f) => {
1245
1324
  if (n === 0)
1246
1325
  return f();
1247
- const by = [(a) => f(a), (a, b) => f(a, b), (a, b, c) => f(a, b, c), (a, b, c, d) => f(a, b, c, d),
1248
- (a, b, c, d, e) => f(a, b, c, d, e), (a, b, c, d, e, g) => f(a, b, c, d, e, g)][n - 1];
1326
+ // A law's keys are counted by its callback's parameters: this one takes n.
1327
+ const by = (...ks) => f(...ks);
1328
+ Object.defineProperty(by, 'length', { value: n });
1249
1329
  return forall(by);
1250
1330
  };
1251
1331
  const facts = (withBetween) => (f, at = {}) => {
@@ -1279,7 +1359,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1279
1359
  };
1280
1360
  const distinctNames = (what, names) => { if (new Set(names).size !== names.length)
1281
1361
  throw new Error(`${what}: a name given twice`); };
1282
- // A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each as a function of its key,
1362
+ // A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each called on its arguments,
1283
1363
  // as the runtimes call it).
1284
1364
  const recurJs = (name, args, zero, next, uses) => {
1285
1365
  const fns = new Map(uses.map(([u], i) => [u, `$f${i}`]));
@@ -1294,7 +1374,10 @@ const recurJs = (name, args, zero, next, uses) => {
1294
1374
  case 'min': return `Math.min(${js(e.a)}, ${js(e.b)})`;
1295
1375
  case 'max': return `Math.max(${js(e.a)}, ${js(e.b)})`;
1296
1376
  case 'if': return `(${jc(e.c)} ? ${js(e.then)} : ${js(e.else)})`;
1297
- case 'get': return `${fns.get(e.map)}(${js(e.key)})`;
1377
+ case 'get': {
1378
+ const i = uses.findIndex(([u]) => u === e.map), args = uses[i][1].arity > 1 ? pairedArgs(e.key, uses[i][1].arity) : undefined;
1379
+ return args ? `$g${i}(${args.map(js).join(', ')})` : `${fns.get(e.map)}(${js(e.key)})`;
1380
+ }
1298
1381
  }
1299
1382
  };
1300
1383
  const jc = (c) => {
@@ -1309,7 +1392,7 @@ const recurJs = (name, args, zero, next, uses) => {
1309
1392
  case 'all': throw new Error('a recurrence takes no flag or quantifier');
1310
1393
  }
1311
1394
  };
1312
- const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); `).join('');
1395
+ const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); ${d.arity > 1 ? `const $g${i} = (${jsOf(d)}); ` : ''}`).join('');
1313
1396
  // The last value computed at each tuple of further arguments is kept, so reading f at x after f at a smaller x takes
1314
1397
  // only the steps between (a rule that reads f at a count that grows costs one step a request).
1315
1398
  return `(() => { ${inlined}const $memo = new Map(); return (x, ${args.map(a => `${a}, `).join('')}) => { const $k = ${args.length ? `[${args.join(', ')}].join()` : "''"}; let $at = $memo.get($k); if (!$at || $at[0] > x) $at = [0, ${js(zero)}]; let x0 = $at[0], prev = $at[1]; for (; x0 < x; x0++) { prev = ((x, prev) => ${js(next)})(x0, prev); if (!Number.isSafeInteger(prev)) throw new RangeError(${JSON.stringify(`${name} passes the safe integers`)}); } if ($memo.size > 4096) $memo.clear(); $memo.set($k, [x, prev]); return prev; }; })()`;
@@ -1322,8 +1405,9 @@ const flatFns = (defs) => defs.flatMap(([n, d]) => {
1322
1405
  const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
1323
1406
  const argsKey = (args) => args.slice(1).reduce(pairE, args[0]);
1324
1407
  // A function's JavaScript as one function of its key: its arguments unpaired (left-nested, as argsKey pairs them).
1408
+ const jsOf = (d) => (typeof d.js === 'string' ? d.js : String(d.js));
1325
1409
  const fnSource = (name, d) => {
1326
- const src = typeof d.js === 'string' ? d.js : String(d.js);
1410
+ const src = jsOf(d);
1327
1411
  let f;
1328
1412
  try {
1329
1413
  f = compileFunction([], `return (${src})`)();
@@ -1446,6 +1530,19 @@ export class Rule {
1446
1530
  return this.with({ maps: [...this.spec.maps, ...entries.flatMap(([n, k]) => k.vars(n).map(part => [part, 0]))],
1447
1531
  state: [...this.spec.state, ...entries.map(([n]) => [`${n}_len`, 0])], items: [...(this.spec.items ?? []), ...entries] });
1448
1532
  }
1533
+ // A table of nodes, empty at the start (NodeTable): a tree in the state, made one node a step.
1534
+ nodes(name, of, children, folds = {}) {
1535
+ for (const c of children)
1536
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c) || c === 'value')
1537
+ throw new Error(`${name}: ${c} can't name a child`);
1538
+ for (const f of Object.keys(folds))
1539
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
1540
+ throw new Error(`${name}: ${f} is not a plain fold name`);
1541
+ distinctNames(name, [...children]);
1542
+ const maps = [...of.vars(name).map(part => [part, 0]), ...children.map(c => [`${name}^${c}`, 0]),
1543
+ ...Object.entries(folds).map(([f, d]) => [`${name}$${f}`, d.empty])];
1544
+ return this.with({ maps: [...this.spec.maps, ...maps], state: [...this.spec.state, [`${name}_size`, 0]], nodes: [...(this.spec.nodes ?? []), [name, of, [...children], folds]] });
1545
+ }
1449
1546
  // Functions defined outside the rule (fn, divBy, modBy), read like params that take arguments: f(x).
1450
1547
  fns(defs) {
1451
1548
  return this.with({ fns: [...(this.spec.fns ?? []), ...Object.entries(defs)] });
@@ -1456,6 +1553,7 @@ export class Rule {
1456
1553
  carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
1457
1554
  // A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
1458
1555
  // .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
1556
+ // Each fold's start and step, and each lemma, may read the params declared before it.
1459
1557
  carriesList(name, of, folds = {}, lemmas = {}) {
1460
1558
  const entries = [[name, of, folds, lemmas]];
1461
1559
  for (const [n, , fs] of entries)
@@ -1464,7 +1562,7 @@ export class Rule {
1464
1562
  throw new Error(`${n}: ${f} is not a plain fold name`);
1465
1563
  // The folds' lemmas, proven now (a lemma that isn't throws, with the verdict).
1466
1564
  for (const [n, k, fs, ls] of entries) {
1467
- const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls).prove();
1565
+ const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, undefined, this.spec.params).prove();
1468
1566
  if (!(v2.proven && v2.checked))
1469
1567
  throw new Unproven(`${n}'s fold lemmas`, v2);
1470
1568
  }
@@ -1530,6 +1628,12 @@ export class Rule {
1530
1628
  if (k.invariant(k.value(n)).length) {
1531
1629
  out.push(this.law(`every ${n} cell is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
1532
1630
  }
1631
+ for (const [n, k] of this.spec.nodes ?? []) {
1632
+ if (k.invariant(k.value(n)).length)
1633
+ out.push(this.law(`every ${n} node is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
1634
+ for (const [name] of this.env({ flags: false, inputs: false, queries: false })[n].laws())
1635
+ out.push(this.law(name, env => of(env, n).laws().find(([m]) => m === name)[1]));
1636
+ }
1533
1637
  return out;
1534
1638
  }
1535
1639
  // A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
@@ -1547,7 +1651,7 @@ export class Rule {
1547
1651
  const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
1548
1652
  return withSource(more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w }, src);
1549
1653
  }
1550
- // A law about k steps in a row (k from 2 to 16): `then(first, last, requests)` relates what the first step reads to the
1654
+ // A law about k steps in a row (k from 2 to 64): `then(first, last, requests)` relates what the first step reads to the
1551
1655
  // state, maps and sums after the last, with each step's request and queries (requests[0] is the first's); `when(before,
1552
1656
  // j)` narrows the j-th step's request (j from 0), read at the state that step starts from.
1553
1657
  // meter.stepsLaw('two requests spend at most 2m', 2, ({spent, m}, after) => after.spent.atMost(spent.plus(m).plus(m)))
@@ -1649,8 +1753,9 @@ export class Rule {
1649
1753
  out[n] = k.value(n);
1650
1754
  for (const n of this.spec.carried ?? [])
1651
1755
  out[n] = new MapRef(n, 0);
1756
+ const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
1652
1757
  for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
1653
- const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls);
1758
+ const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
1654
1759
  for (const part of l.maps)
1655
1760
  delete out[part];
1656
1761
  out[n] = l;
@@ -1697,6 +1802,15 @@ export class Rule {
1697
1802
  delete out[part];
1698
1803
  out[n] = new ItemsRef(n, k, len(n), part => new ListRef(at(part), len(n)));
1699
1804
  }
1805
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1806
+ const tables = {};
1807
+ for (const [n, k, cs, fs] of this.spec.nodes ?? []) {
1808
+ const t = new NodeTable(n, k, cs, fs, v(at(`${n}_size`)), part => new MapRef(at(part), init.get(part)), { ...tables });
1809
+ tables[n] = t;
1810
+ for (const part of [...k.vars(n), ...cs.map(c => `${n}^${c}`), ...Object.keys(fs).map(f => `${n}$${f}`)])
1811
+ delete out[part];
1812
+ out[n] = t;
1813
+ }
1700
1814
  }
1701
1815
  // Every name the rule declares is declared once (a flag named again replaces the earlier one, so flags are checked as
1702
1816
  // they are defined): an input and a state variable sharing a name would hand the callbacks only one of them.
@@ -1705,6 +1819,7 @@ export class Rule {
1705
1819
  return [...s.params.map(([n]) => [n, 'a param']), ...(s.fns ?? []).map(([n]) => [n, 'a function']), ...s.inputs.map(([n]) => [n, 'an input']), ...(s.carried ?? []).map(n => [n, 'a carried map']),
1706
1820
  ...s.state.map(([n]) => [n, 'a state variable']), ...s.maps.map(([n]) => [n, 'a map']), ...s.sums.map(([n]) => [n, 'a sum']), ...s.queries.map(([n]) => [n, 'a query']),
1707
1821
  ...[...(s.cells ?? []), ...(s.items ?? [])].filter(([n, k]) => !k.vars(n).includes(n)).map(([n]) => [n, 'a map of a kind']),
1822
+ ...(s.nodes ?? []).map(([n]) => [n, 'a table of nodes']),
1708
1823
  ];
1709
1824
  }
1710
1825
  whatIs(n) { return this.names().find(([m]) => m === n)?.[1] ?? 'something else'; }
@@ -1770,7 +1885,7 @@ export class Rule {
1770
1885
  // Functions, and their facts: each function's own (named after it), then the rule's.
1771
1886
  // The functions a function reads come first, each named <function>.<use>.
1772
1887
  const flat = flatFns(s.fns ?? []);
1773
- const fns = Object.fromEntries(flat.map(([n, d]) => [n, { js: fnSource(n, d), ...(d.lean ? { lean: d.lean } : {}), ...(d.recur ? { recur: d.recur } : {}) }]));
1888
+ const fns = Object.fromEntries(flat.map(([n, d]) => [n, { js: fnSource(n, d), ...(d.arity > 1 ? { arity: d.arity, call: jsOf(d) } : {}), ...(d.lean ? { lean: d.lean } : {}), ...(d.recur ? { recur: d.recur } : {}) }]));
1774
1889
  const facts = [
1775
1890
  ...flat.flatMap(([n, d, uses]) => Object.entries(d.facts?.(fnTerm(n, d), uses) ?? {}).map(([name, f]) => this.lawOf(`${n}: ${name}`, f))),
1776
1891
  ...(s.facts ?? []).flatMap(f => Object.entries(f(pEnv)).map(([name, out]) => this.lawOf(name, out))),
@@ -1837,7 +1952,7 @@ export class Rule {
1837
1952
  }
1838
1953
  if (d === (opts.depth ?? 8))
1839
1954
  break;
1840
- const input = this.randomInput(rand, bound);
1955
+ const input = this.randomInput(rand, bound, params);
1841
1956
  if (s.prune || !allowed(m, run, params, st, input))
1842
1957
  continue;
1843
1958
  d++;
@@ -1961,7 +2076,7 @@ export class Rule {
1961
2076
  const edges = trial === 0 ? opts.edges ?? [] : [];
1962
2077
  for (let d = 0; d < (opts.depth ?? 10) + edges.length; d++) {
1963
2078
  const request = edges[d] ?? opts.draw?.(rand) ?? sample(s.inputs);
1964
- ours = gen.step(ps, ours, this.encodeInput(request));
2079
+ ours = gen.step(ps, ours, this.encodeInput(request, params));
1965
2080
  theirs = reference.step(params, theirs, request);
1966
2081
  const got = this.decode(ours);
1967
2082
  for (const [field, want] of Object.entries(theirs)) {
@@ -1977,10 +2092,12 @@ export class Rule {
1977
2092
  // Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
1978
2093
  encodeParams(values) { return encode(this.spec.params, values); }
1979
2094
  // A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural.
1980
- // A carried list is an array of its items' runtime values (its length and folds computed from them).
2095
+ // A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
2096
+ // folds that read the params are computed with.
1981
2097
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1982
- encodeInput(values) {
1983
- const lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0)).encode(values[n])]));
2098
+ encodeInput(values, params) {
2099
+ const at = params && this.paramValues(this.encodeParams(params));
2100
+ const lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, at).encode(values[n])]));
1984
2101
  const out = encode(this.spec.inputs, { ...values, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
1985
2102
  for (const l of Object.values(lists))
1986
2103
  Object.assign(out, l.maps);
@@ -1994,13 +2111,18 @@ export class Rule {
1994
2111
  }
1995
2112
  // Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
1996
2113
  randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
1997
- randomInput(rand, bound) {
2114
+ // The params' values by their kinds, as constants (what a fold that reads them is computed with).
2115
+ paramValues(ps) {
2116
+ return Object.fromEntries(this.spec.params.map(([n, k]) => [n, k.build(n, part => new Term({ op: 'const', value: ps[part] }, ps[part]))]));
2117
+ }
2118
+ // `ps`: the params (encoded), for folds that read them.
2119
+ randomInput(rand, bound, ps) {
1998
2120
  const out = Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound)));
1999
2121
  for (const n of this.spec.carried ?? [])
2000
2122
  out[n] = new Map(Array.from({ length: bound + 1 }, (_, key) => [key, rand(2)]).filter(([, cell]) => cell === 1));
2001
2123
  // A carried list: items drawn by their kind, its length and folds as the encoding computes them.
2002
2124
  for (const [n, k, fs] of this.spec.lists2 ?? []) {
2003
- const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
2125
+ const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, ps && this.paramValues(ps)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
2004
2126
  Object.assign(out, l.maps, { [`${n}_len`]: l.len });
2005
2127
  }
2006
2128
  return out;
@@ -2022,6 +2144,11 @@ export class Rule {
2022
2144
  }
2023
2145
  for (const [n, k] of this.spec.items ?? [])
2024
2146
  out[n] = Array.from({ length: st.scalars[`${n}_len`] }, (_, i) => cell(n, k, i));
2147
+ // A table of nodes: node 1 first, each its value and its children.
2148
+ for (const [n, k, cs] of this.spec.nodes ?? []) {
2149
+ const at = (part, key) => st.maps[part]?.get(key) ?? 0;
2150
+ out[n] = Array.from({ length: st.scalars[`${n}_size`] }, (_, i) => ({ value: cell(n, k, i + 1), ...Object.fromEntries(cs.map(c => [c, at(`${n}^${c}`, i + 1)])) }));
2151
+ }
2025
2152
  return out;
2026
2153
  }
2027
2154
  // The prune order's variable, if the history is pruned (a runtime must send it nondecreasing).
@@ -2105,7 +2232,7 @@ export class Fun {
2105
2232
  this.compiledRule ??= (rule => ({ rule, gen: rule.compiled() }))(this.rule);
2106
2233
  const { rule, gen } = this.compiledRule, ps = rule.encodeParams(params);
2107
2234
  const kinds = Object.entries(this.result(this.r['env']({ flags: false, state: false, queries: false })));
2108
- const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values)));
2235
+ const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values, params)));
2109
2236
  return Object.fromEntries(kinds.map(([k]) => [k, st[`out.${k}`]]));
2110
2237
  }
2111
2238
  }
@@ -2212,7 +2339,7 @@ export function crossCheckDecision(d, opts = {}) {
2212
2339
  const r = d.rule, m = r.machine, bound = opts.bound ?? 4, rand = opts.rand ?? seeded(11), own = d.given.slice(d.given.length - src.own);
2213
2340
  let checked = 0;
2214
2341
  for (let i = 0; i < (opts.trials ?? 500); i++) {
2215
- const input = r.randomInput(rand, bound), values = { ...r.randomParams(rand, bound), ...numbersOf(input) };
2342
+ const ps = r.randomParams(rand, bound), input = r.randomInput(rand, bound, ps), values = { ...ps, ...numbersOf(input) };
2216
2343
  const st = { scalars: {}, maps: Object.fromEntries(Object.entries(input).filter((e) => typeof e[1] !== 'number').map(([n, cells]) => [n, new Map(cells)])) };
2217
2344
  const keys = exactKeys(keysOf(bound, st), values, st);
2218
2345
  const sc = { vars: values, maps: Object.fromEntries(Object.entries(st.maps).map(([n, cells]) => [n, { cells, dflt: 0 }])), keys, flags: {} };
@@ -2243,6 +2370,11 @@ export class FoldRule {
2243
2370
  assume(f) { return this.with({ assume: [...this.spec.assume, f] }); }
2244
2371
  leaf(f) { return this.with({ leaf: f }); }
2245
2372
  node(f) { return this.with({ node: f }); }
2373
+ // Nodes with any number of children (RoseFold): the children's values folded into `kids`, from `from` by `step` (which
2374
+ // reads the params), each lemma a law of every prefix of every node's children that meet the fold's laws.
2375
+ children(from, step, lemmas = {}) {
2376
+ return new RoseFold(this.spec, { from: from, step: step, lemmas: lemmas });
2377
+ }
2246
2378
  env(inputs = true) {
2247
2379
  const out = {};
2248
2380
  for (const [n, k] of this.spec.params)
@@ -2337,4 +2469,151 @@ export class FoldRule {
2337
2469
  }
2338
2470
  }
2339
2471
  export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
2340
- export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, laws));
2472
+ // A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
2473
+ export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, [...(f.lemmas ?? []), ...laws]));
2474
+ const asTerm = (t) => (t instanceof Term ? t : lit(t));
2475
+ // A fold over trees whose nodes have any number of children, as the kernel's binary fold (so the kernel and Lean's
2476
+ // fold_sound check it as they check any fold): a node's children become a chain of glue nodes, each folding one child
2477
+ // into the values of the rest (`kids`, from `from` at the chain's end), and the node reads the chain's values. Every
2478
+ // tree node carries `kind$` (0 a node or leaf of the tree, 1 a glue node or the chain's end) and its role as an input; a
2479
+ // law of the fold is claimed where kind$ is 0, each lemma where it is 1. A binary tree no rose tree maps to (a child
2480
+ // where a chain belongs, or a chain where a child does) is read as one that does: a node reads a chain only, a glue
2481
+ // node a child only, so the laws are proven of every binary tree, and of each rose tree's image in particular.
2482
+ // fold('org chart').params({cap: nat}).inputs({pay: nat})
2483
+ // .leaf(({pay}) => ({cost: pay, heads: 1}))
2484
+ // .children({cost: 0, heads: 0}, (acc, kid) => ({cost: acc.cost.plus(kid.cost), heads: acc.heads.plus(kid.heads)}))
2485
+ // .node(({pay}, kids) => ({cost: kids.cost.plus(pay), heads: kids.heads.plus(1)}))
2486
+ export class RoseFold {
2487
+ spec;
2488
+ kids;
2489
+ nodeOf;
2490
+ constructor(spec, kids, nodeOf) {
2491
+ this.spec = spec;
2492
+ this.kids = kids;
2493
+ this.nodeOf = nodeOf;
2494
+ }
2495
+ get name() { return this.spec.name; }
2496
+ node(f) { return new RoseFold(this.spec, this.kids, f); }
2497
+ env(inputs = true) {
2498
+ const out = {};
2499
+ for (const [n, k] of this.spec.params)
2500
+ out[n] = k.value(n);
2501
+ if (inputs)
2502
+ for (const [n, k] of this.spec.inputs)
2503
+ out[n] = k.value(n);
2504
+ return out;
2505
+ }
2506
+ names() {
2507
+ if (!this.spec.leaf)
2508
+ throw new Error(`${this.spec.name}: a fold needs a leaf`);
2509
+ return Object.keys(this.spec.leaf(this.env()));
2510
+ }
2511
+ side(prefix) { return Object.fromEntries(this.names().map(n => [n, v(`${prefix}${n}`)])); }
2512
+ accs(prefix) { return Object.fromEntries(Object.keys(this.kids.from).map(a => [a, v(`${prefix}kids$${a}`)])); }
2513
+ start(p) { return Object.fromEntries(Object.entries(this.kids.from).map(([a, x]) => [a, typeof x === 'number' ? lit(x) : asTerm(x(p))])); }
2514
+ // A law about the values and the params, claimed of every node and leaf of the tree.
2515
+ law(name, f) { return { name, holds: v('kind$').atLeast(1).or(f({ ...this.env(false), ...this.side('') })).c }; }
2516
+ // The children's lemmas (claimed of every glue node), and that kind$ is 0 or 1: proveFold adds them.
2517
+ get lemmas() {
2518
+ const p = this.env(false);
2519
+ return [{ name: `${this.spec.name}: kind$ is 0 or 1`, holds: v('kind$').atMost(1).c },
2520
+ ...Object.entries(this.kids.lemmas).map(([n, l]) => ({ name: n, holds: v('kind$').eq(0).or(l(this.accs(''), p)).c }))];
2521
+ }
2522
+ get fold() {
2523
+ const s = this.spec, node = this.nodeOf;
2524
+ if (!s.leaf || !node)
2525
+ throw new Error(`${s.name}: a fold needs a leaf and a node`);
2526
+ const env = this.env(), pEnv = this.env(false), names = this.names(), accNames = Object.keys(this.kids.from);
2527
+ const role = v('role$'), real = role.eq(0);
2528
+ const pre = [...s.params.flatMap(([n, k]) => k.assume(n)).map(c => c.c), ...s.pre.map(f => f(pEnv).c)];
2529
+ const own = all(...s.inputs.flatMap(([n, k]) => k.assume(n)), ...s.assume.map(f => f(env)));
2530
+ const assume = role.atMost(1).and(role.eq(1).or(own)).c;
2531
+ const start = this.start(pEnv);
2532
+ // A leaf of the tree, or the end of a chain (no children yet).
2533
+ const leafOut = s.leaf(env);
2534
+ const leaf = { ...Object.fromEntries(names.map(n => [n, when(real, leafOut[n], 0).e])),
2535
+ ...Object.fromEntries(accNames.map(a => [`kids$${a}`, when(real, 0, start[a]).e])), 'kind$': when(real, 0, 1).e };
2536
+ // A node reads the chain on its left (its children's values); a glue node folds its left child into the rest.
2537
+ const lAcc = this.accs('l_'), rAcc = this.accs('r_'), lk = v('l_kind$'), rk = v('r_kind$');
2538
+ const kidsIn = Object.fromEntries(accNames.map(a => [a, when(lk.eq(1), lAcc[a], start[a])]));
2539
+ const rest = Object.fromEntries(accNames.map(a => [a, when(rk.eq(1), rAcc[a], start[a])]));
2540
+ const nodeOut = node(env, kidsIn), stepped = this.kids.step(rest, this.side('l_'), pEnv);
2541
+ const nodeE = { ...Object.fromEntries(names.map(n => [n, when(real, nodeOut[n], 0).e])),
2542
+ ...Object.fromEntries(accNames.map(a => [`kids$${a}`, when(real, 0, when(lk.eq(0), stepped[a], rest[a])).e])), 'kind$': when(real, 0, 1).e };
2543
+ return { name: s.name, params: s.params.flatMap(([n, k]) => k.vars(n)), inputs: [...s.inputs.flatMap(([n, k]) => k.vars(n)), 'role$'], leaf, node: nodeE,
2544
+ ...(pre.length ? { pre: pre.length === 1 ? pre[0] : { op: 'and', xs: pre } } : {}), assume };
2545
+ }
2546
+ // The values of a rose tree, by the terms proven (the tree as the binary tree the fold reads it as).
2547
+ run(tree, params = {}) {
2548
+ const f = this.fold, ps = encode(this.spec.params, params), none = { name: f.name, params: [], inputs: [], state: {}, step: {} };
2549
+ const st = { scalars: {}, maps: {} };
2550
+ if (f.pre && !evalC(f.pre, ps, st, none))
2551
+ throw new RangeError(`${f.name}: the params don't meet its precondition`);
2552
+ const zero = Object.fromEntries(f.inputs.map(n => [n, 0]));
2553
+ const at = (exprs, env) => Object.fromEntries(Object.entries(exprs).map(([n, e]) => [n, evalE(e, env, st, none)]));
2554
+ const kidsOf = (vals, prefix) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [`${prefix}${n}`, x]));
2555
+ const chain = (cs) => {
2556
+ const env = { ...ps, ...zero, 'role$': 1 };
2557
+ if (!cs.length)
2558
+ return at(f.leaf, env);
2559
+ return at(f.node, { ...env, ...kidsOf(go(cs[0]), 'l_'), ...kidsOf(chain(cs.slice(1)), 'r_') });
2560
+ };
2561
+ const go = (t) => {
2562
+ const own = 'leaf' in t ? t.leaf : t.node, env = { ...ps, ...encode(this.spec.inputs, own), 'role$': 0 };
2563
+ if (f.assume && !evalC(f.assume, env, st, none))
2564
+ throw new RangeError(`${f.name}: ${JSON.stringify(own)} doesn't meet its assumption`);
2565
+ if ('leaf' in t)
2566
+ return at(f.leaf, env);
2567
+ return at(f.node, { ...env, ...kidsOf(chain(t.children), 'l_'), ...kidsOf(chain([]), 'r_') });
2568
+ };
2569
+ const out = go(tree);
2570
+ return Object.fromEntries(this.names().map(n => [n, out[n]]));
2571
+ }
2572
+ // The builders against what they built, on random rose trees: each value by the callbacks' own arithmetic (the
2573
+ // children folded by step, then the node) against the terms proven, and each law and lemma at the root.
2574
+ crossCheck(laws = [], opts = {}) {
2575
+ const s = this.spec, rand = opts.rand ?? seeded(17), bound = opts.bound ?? 6, f = this.fold;
2576
+ let checked = 0;
2577
+ const sample = (kinds) => Object.fromEntries(kinds.map(([n, k]) => [n, k.sample(rand, bound)]));
2578
+ const tree = (d) => (d === 0 || rand(3) === 0 ? { leaf: sample(s.inputs) } : { node: sample(s.inputs), children: Array.from({ length: rand(4) }, () => tree(d - 1)) });
2579
+ const known = (vals) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [n, new Term({ op: 'const', value: x }, x)]));
2580
+ const nums = (out) => { const vals = Object.entries(out).map(([n, x]) => [n, valOf(x)]); return vals.every(([, x]) => x !== undefined) ? Object.fromEntries(vals) : undefined; };
2581
+ for (let t = 0; t < (opts.trials ?? 200); t++) {
2582
+ const params = sample(s.params), ps = encode(s.params, params);
2583
+ const pv = known(ps), pEnv = Object.fromEntries(s.params.map(([n, k]) => [n, k.build(n, part => pv[part])]));
2584
+ const builder = (tr) => {
2585
+ const own = 'leaf' in tr ? tr.leaf : tr.node, iv = known(encode(s.inputs, own)), env = { ...pEnv, ...Object.fromEntries(s.inputs.map(([n, k]) => [n, k.build(n, part => iv[part])])) };
2586
+ if ('leaf' in tr)
2587
+ return nums(s.leaf(env));
2588
+ let acc = nums(this.start(pEnv));
2589
+ for (const c of [...tr.children].reverse()) {
2590
+ const kid = builder(c);
2591
+ acc = acc && kid && nums(this.kids.step(known(acc), known(kid), pEnv));
2592
+ }
2593
+ return acc && nums(this.nodeOf(env, known(acc)));
2594
+ };
2595
+ const tr = tree(opts.depth ?? 3);
2596
+ let ours;
2597
+ try {
2598
+ ours = this.run(tr, params);
2599
+ }
2600
+ catch {
2601
+ continue;
2602
+ }
2603
+ const theirs = builder(tr);
2604
+ if (!theirs)
2605
+ continue;
2606
+ for (const [n, x] of Object.entries(theirs)) {
2607
+ checked++;
2608
+ if (ours[n] !== x)
2609
+ return { checked, failure: `${f.name}: ${n} is ${x} by its callbacks, ${ours[n]} by the terms proven, for ${JSON.stringify(tr)}` };
2610
+ }
2611
+ for (const law of laws) {
2612
+ checked++;
2613
+ if (!evalC(law.holds, { ...ps, ...ours, 'kind$': 0 }, { scalars: {}, maps: {} }, { name: f.name, params: [], inputs: [], state: {}, step: {} }))
2614
+ return { checked, failure: `${f.name}: law ${law.name} fails for ${JSON.stringify(tr)} with params ${JSON.stringify(params)}` };
2615
+ }
2616
+ }
2617
+ return { checked };
2618
+ }
2619
+ }
package/dist/core/gdp.js CHANGED
@@ -21,7 +21,7 @@ export function guard(prover, fn, field, opts) {
21
21
  if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
22
22
  throw new Error(`${fn.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
23
23
  return (args, ...about) => {
24
- const input = r.encodeInput(args);
24
+ const input = r.encodeInput(args, params);
25
25
  if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
26
26
  return null;
27
27
  return atPath(fn.run(args, params), field) === true ? prover.prove(...about) : null;
@@ -46,7 +46,7 @@ export function gate(prover, r, flag, opts) {
46
46
  let st = opts.state ?? compiled.init(ps);
47
47
  return {
48
48
  request: (args, ...about) => {
49
- const input = r.encodeInput(args);
49
+ const input = r.encodeInput(args, params);
50
50
  if (!allowed(m, run, ps, st, input))
51
51
  return null;
52
52
  const granted = requestEnv(m, run, ps, st, input)[`?${flag}`] === 1;
@@ -71,6 +71,8 @@ export type Fn = {
71
71
  js: string;
72
72
  lean?: string;
73
73
  recur?: Recurrence;
74
+ arity?: number;
75
+ call?: string;
74
76
  };
75
77
  export type Recurrence = {
76
78
  args: string[];
@@ -162,6 +164,7 @@ export type Request = Record<string, number | ReadonlyMap<number, number>>;
162
164
  export declare const naturalIn: (v: unknown, what: string) => number;
163
165
  export declare function evalE(e: E, env: Env, st: State, m: Machine): number;
164
166
  export declare const fnValue: (fn: Fn, key: number) => number;
167
+ export declare function pairedArgs(key: E, n: number): E[] | undefined;
165
168
  export declare function evalC(c: C, env: Env, st: State, m: Machine): boolean;
166
169
  export declare function carriedView(m: Machine, st: State, input: Request, rename?: (map: string) => string): State;
167
170
  export declare const numbersOf: (input: Request) => Record<string, number>;
@@ -130,7 +130,12 @@ export function evalE(e, env, st, m) {
130
130
  case 'max': return Math.max(evalE(e.a, env, st, m), evalE(e.b, env, st, m));
131
131
  case 'if': return evalC(e.c, env, st, m) ? evalE(e.then, env, st, m) : evalE(e.else, env, st, m);
132
132
  case 'get': {
133
- const key = evalE(e.key, env, st, m), fn = m.fns?.[e.map];
133
+ const fn = m.fns?.[e.map], args = fn?.call ? pairedArgs(e.key, fn.arity) : undefined;
134
+ if (args) {
135
+ const xs = args.map(a => evalE(a, env, st, m));
136
+ return naturalIn(callOf(fn)(...xs), `${e.map}(${xs.join(', ')})`);
137
+ }
138
+ const key = evalE(e.key, env, st, m);
134
139
  return fn ? naturalIn(fnOf(fn)(key), `${e.map}(${key})`) : st.maps[e.map].get(key) ?? st.defaults?.[e.map] ?? m.maps[e.map];
135
140
  }
136
141
  }
@@ -147,6 +152,34 @@ const fnOf = (fn) => {
147
152
  }
148
153
  return f;
149
154
  };
155
+ const compiledCalls = new WeakMap();
156
+ const callOf = (fn) => {
157
+ let f = compiledCalls.get(fn);
158
+ if (!f) {
159
+ f = compileFunction([], `return (${fn.call})`)();
160
+ compiledCalls.set(fn, f);
161
+ }
162
+ return f;
163
+ };
164
+ // The n arguments a key pairs (left-nested, ((a, b), c)..., each pair (s)² + b with s = a + b), if it is their pairing.
165
+ const paired = new WeakMap();
166
+ export function pairedArgs(key, n) {
167
+ let byN = paired.get(key);
168
+ if (!byN)
169
+ paired.set(key, (byN = new Map()));
170
+ if (byN.has(n))
171
+ return byN.get(n);
172
+ let out;
173
+ if (n === 1)
174
+ out = [key];
175
+ else if (key.op === 'add' && key.a.op === 'prod' && key.a.a.op === 'add' && alike(key.a.a, key.a.b) && alike(key.a.a.b, key.b)) {
176
+ const first = pairedArgs(key.a.a.a, n - 1);
177
+ out = first && [...first, key.b];
178
+ }
179
+ byN.set(n, out);
180
+ return out;
181
+ }
182
+ const alike = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
150
183
  export function evalC(c, env, st, m) {
151
184
  switch (c.op) {
152
185
  case '<': return evalE(c.a, env, st, m) < evalE(c.b, env, st, m);
@@ -323,7 +356,12 @@ export function compileJs(m) {
323
356
  case 'min': return `Math.min(${ex(e.a)}, ${ex(e.b)})`;
324
357
  case 'max': return `Math.max(${ex(e.a)}, ${ex(e.b)})`;
325
358
  case 'if': return `(${cx(e.c)} ? ${ex(e.then)} : ${ex(e.else)})`;
326
- case 'get': return fns.includes(e.map) ? `$n(fn${fns.indexOf(e.map)}(${ex(e.key)}), ${id(`${e.map}()`)})` : `(maps[${id(e.map)}].get(${ex(e.key)}) ?? ${carriedMaps.includes(e.map) ? '0' : `dft[${id(e.map)}] ?? ${plain.maps[e.map]}`})`;
359
+ case 'get': {
360
+ const fn = m.fns?.[e.map], args = fn?.call ? pairedArgs(e.key, fn.arity) : undefined;
361
+ if (args)
362
+ return `$n(fnc${fns.indexOf(e.map)}(${args.map(ex).join(', ')}), ${id(`${e.map}()`)})`;
363
+ return fns.includes(e.map) ? `$n(fn${fns.indexOf(e.map)}(${ex(e.key)}), ${id(`${e.map}()`)})` : `(maps[${id(e.map)}].get(${ex(e.key)}) ?? ${carriedMaps.includes(e.map) ? '0' : `dft[${id(e.map)}] ?? ${plain.maps[e.map]}`})`;
364
+ }
327
365
  }
328
366
  };
329
367
  const cx = (c) => {
@@ -379,7 +417,7 @@ function $n(v, what) { if (typeof v !== 'number' || !Number.isSafeInteger(v) ||
379
417
  function $x(v) { if (v > 9007199254740991) throw new RangeError('a value passed 2^53 - 1, past which the proofs over the naturals no longer describe the run'); return v; }
380
418
  function $m(v, what) { if (!(v instanceof Map)) throw new RangeError(what + ' must be a Map of naturals'); for (const [key, x] of v) { $n(key, 'a key of ' + what); $n(x, 'a cell of ' + what); } return v; }
381
419
  function $keys(maps, names) { const keys = new Set(); let past = 0; for (const c of Object.values(maps)) for (const key of c.keys()) past = Math.max(past, key + 1); for (const n of names) for (const key of maps[n].keys()) keys.add(key); return [...keys, past]; }
382
- ${fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n`).join('')}function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }
420
+ ${fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n${m.fns[name].call ? `const fnc${i} = (${m.fns[name].call});\n` : ''}`).join('')}function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }
383
421
  function $dense(keys, f) { const top = keys[keys.length - 1]; if (top > 1000000) throw new RangeError('a write at every key reads its key in arithmetic past a million keys (' + top + ')'); const d = f(top), cells = new Map(); for (let k = 0; k < top; k++) { const v = f(k); if (v !== d) cells.set(k, v); } return {cells, d}; }
384
422
  function $every(keys, f) { const past = keys[keys.length - 1], d = f(past), cells = new Map(); for (let i = 0; i < keys.length - 1; i++) { const v = f(keys[i]); if (v !== d) cells.set(keys[i], v); } return {cells, d}; }
385
423
  export function init(params) {
@@ -1042,8 +1042,9 @@ export function stepLawObligations(machine, laws, instances, sl) {
1042
1042
  const m = machine.history ? historyMachine(machine).machine : machine;
1043
1043
  scope(m, laws);
1044
1044
  const k = sl.steps ?? 1;
1045
- if (!Number.isInteger(k) || k < 1 || k > 16)
1046
- throw new Error(`step law ${sl.name}: steps is a whole number from 1 to 16`);
1045
+ // Past 64 the cost is the bound (proving grows about as k³: 64 steps in about 90 s; Lean checks 32 in about 13 minutes).
1046
+ if (!Number.isInteger(k) || k < 1 || k > 64)
1047
+ throw new Error(`step law ${sl.name}: steps is a whole number from 1 to 64`);
1047
1048
  const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
1048
1049
  if (whens.length > k)
1049
1050
  throw new Error(`step law ${sl.name}: one when per step at most`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdp-wired",
3
- "version": "0.10.1",
3
+ "version": "0.11.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",
@@ -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 && 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/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 && 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/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",