gdp-wired 0.8.0 → 0.10.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
@@ -67,6 +67,10 @@ All of these are proven automatically, and each proof is also a Lean theorem:
67
67
  - **Records and variants**: inputs, state, results, map cells and list items made of named fields, or one of several
68
68
  cases each with fields of its own (a payment by card or in cash, a seat free, held or sold), every case covered
69
69
  (`examples/datatypes.ts`).
70
+ - **Lists a request carries, and trees**: an invoice's lines, with folds over them (the total, the taxable part) and
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`).
70
74
  - **Pure functions and two-run laws**: laws about what a function returns, and laws comparing two calls, like "the
71
75
  secret never changes the answer". `examples/jev-fabric/` ports a Bend project's policy core and proves all 59 laws its
72
76
  Bend proofs state.
@@ -209,13 +213,28 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
209
213
  - **Records and variants**: `record({limit: nat, blocked: bool})` hands out an object of its fields' values;
210
214
  `variant({card: {...}, cash: {...}})` a value with `is(case)` and `match({card: c => ..., cash: c => ...})`, the only
211
215
  place a case's fields are read. `Pay.make({tag: 'card', amount, verified})` builds a case to write or to pass to
212
- `when`, and `same(a, b)` compares two values of one kind part by part. At runtime a record is an object of its fields
213
- and a variant `{tag: 'card', ...fields}`; a field or case the kind doesn't declare is refused. Both are their fields'
216
+ `when`, and `same(a, b)` compares two values of one kind part by part; `optional(c, value)` is a value present where c
217
+ holds (a result that may be missing). At runtime a record is an object of its fields and a variant `{tag: 'card',
218
+ ...fields}` (or the key given, `variant({...}, {tag: 'kind'})`); a field or case the kind doesn't declare is refused.
219
+ What a kind keeps true of the values a step writes (a case in range, a flag 0 or 1) is a law of its own, `r.kindLaws()`,
220
+ which proveRule, proveSteps and proveLeadsTo prove beside the laws they are given. Both are their fields'
214
221
  variables underneath (`pay.card.amount`, and the case's index as `pay`), so the kernel, both runtimes and the Lean
215
222
  twin see nothing new. Maps and lists of them: `.maps({Acct: record({...}).from({...})})` (`Acct.at(k)`,
216
223
  `Acct.set(k, value)`, `Acct.each(k => value)`) and `.items({Queue: Job})` (`push`, `pop`, `set`, `insertAt`,
217
224
  `removeAt`, `apply`, `has`, `within`, `increasing(by)`), one map per variable of the kind; `rule.decode` reads them back
218
- as objects and arrays. Not as values: recursive types (a tree).
225
+ as objects and arrays.
226
+ - **Lists a request carries** (`.carriesList('Lines', Line, folds, lemmas)`): the items one carried map per variable of
227
+ the kind, the length `Lines.len`, each fold (`{total: {from: 0, step: (acc, line) => acc.plus(line.amount)}}`) a
228
+ carried map of its running values whose equations every request is assumed to meet (the encoding computes them from the
229
+ array, and a request that doesn't meet them isn't allowed), read as `Lines.fold.total`; `Lines.every`, `some`, `has`,
230
+ `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
+ - **Folds over trees** (`fold(name).params(...).inputs(...).leaf(env => ({...})).node((env, left, right) => ({...}))`,
233
+ `proveFold`): a tree as a value (`{leaf: inputs}` or `{node: inputs, left, right}`), each leaf's values from its inputs
234
+ 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
+ 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.
219
238
  - **State of any kind**: `.state({open: bool.from(true), status: oneOf('new', 'done').from('new'), owner: str().from(''), note: maybe(nat).from(null)})`;
220
239
  a step writes each kind's own values, `when(c, a, b)` chooses between any two values of a kind, and `rule.decode(state)`
221
240
  reads them back as runtime values.
@@ -237,15 +256,17 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
237
256
  own), and the Lean export takes them as the hypothesis they are, proving them where the function has a Lean
238
257
  definition. A function defined and proven in Lean is how a rule reaches past what the kernel decides: `examples/functions.ts`.
239
258
  - **Recurrences and their lemmas** (`recur(name, {args, fns, zero, next}, lemmas, between)`): f(0, a..) = zero and
240
- f(x + 1, a..) = next(x, f(x, a..), a..), the further arguments `args` (up to three) fixed along the recursion, each term
241
- the kernel's own or a read of a function in `fns` (`modBy(7)`, another recurrence). Each lemma is a law about every x
259
+ f(x + 1, a..) = next(x, f(x, a..), a..), the further arguments `args` (up to four) fixed along the recursion, each term
260
+ the kernel's own or a read of a function in `fns` (`modBy(7)`, another recurrence), the start's too. Each lemma is a law about every x
242
261
  and f(x), proven when the function is made, all of them together by induction (the recurrence run as a machine, each
243
262
  lemma a law of it, so one lemma may lean on another, and on the facts of the functions it reads); each law `between`
244
263
  is about every x <= y and f(x), f(y) ("never shrinks"), proven by induction on y from x, with the lemmas. One that
245
264
  isn't proven throws `Unproven` with the induction step it breaks at. The two equations, the lemmas and the laws
246
- between are the function's facts. The runtimes compute f by the recurrence (x steps); the Lean export defines
247
- `Recur.<name>` by it (a function of several arguments through an injective pairing, proven so in Lean) and proves
248
- every fact there by induction (`examples/lemmas.ts`).
265
+ between are the function's facts. The runtimes compute f by the recurrence, continuing from the last value computed at
266
+ the same further arguments (a rule that reads f at a growing count takes one step a request); the Lean export defines
267
+ `Recur.<name>` by it (a function of several arguments through an injective pairing, proven so in Lean) and checks the
268
+ kernel's own proof of every lemma and law between: the machine it was proven by, with its trees, and the run of it that
269
+ reaches x and f(x), so a lemma with a product (squares: f(x) = x·x) needs no tactic of Lean's (`examples/lemmas.ts`).
249
270
  - **Liveness** (`rule.leadsTo(name, {from, to, rank, helpful})`, `proveLeadsTo`): whenever `from` holds, `to` holds later in
250
271
  every run that takes each helpful request infinitely often; proven by a rank no step raises while waiting and each
251
272
  helpful request lowers in its region. `rank` may return several terms (compared in order) and `helpful` may be a list
@@ -308,7 +329,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
308
329
  | `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 |
309
330
  | `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
310
331
  | `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
311
- | `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), `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) |
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) |
312
333
  | `docs/` | The comparison with Bend and Lean, and the full scope |
313
334
  | `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
314
335
  | `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/`) |
@@ -1,5 +1,5 @@
1
1
  import { type C, type E, type Law, type Recurrence, type Machine, type Request, type State } from './machine.js';
2
- import { type Goal, type Liveness, type Loop, type StepLaw, type Tree } from '../kernel/kernel.js';
2
+ import { type Fold, type Goal, type Liveness, type Loop, type StepLaw, type Tree } from '../kernel/kernel.js';
3
3
  export type TermLike = Term | number;
4
4
  export declare const termOf: (t: TermLike) => E;
5
5
  export declare class Term {
@@ -43,7 +43,7 @@ export declare function when<K extends string>(c: Cond, then: Choice<K>, otherwi
43
43
  export declare function when(c: Cond, then: Str, otherwise: Str): Str;
44
44
  export declare function when<V>(c: Cond, then: Maybe<V>, otherwise: Maybe<V>): Maybe<V>;
45
45
  export declare function when(c: Cond, then: Cond, otherwise: Cond): Cond;
46
- export declare function when<C extends Cases>(c: Cond, then: Variant<C>, otherwise: Variant<C>): Variant<C>;
46
+ export declare function when<C extends Cases, T extends string>(c: Cond, then: Variant<C, T>, otherwise: Variant<C, T>): Variant<C, T>;
47
47
  export declare function when<T extends Record<string, unknown>>(c: Cond, then: T, otherwise: T): T;
48
48
  export declare const choose: (c: Cond, then: Cond, otherwise: Cond) => Cond;
49
49
  export declare function same(a: unknown, b: unknown): Cond;
@@ -65,6 +65,7 @@ export declare abstract class Kind<V> {
65
65
  abstract assume(name: string): Cond[];
66
66
  abstract build(name: string, get: (part: string) => Term): V;
67
67
  value(name: string, at?: At): V;
68
+ invariant(_value: V): Cond[];
68
69
  abstract encode(name: string, raw: Raw): Record<string, number>;
69
70
  abstract random(name: string, rand: Rand, bound: number): Record<string, number>;
70
71
  abstract store(name: string, value: unknown): Record<string, TermLike>;
@@ -152,6 +153,7 @@ export declare class Maybe<V> {
152
153
  }): Term;
153
154
  }
154
155
  export declare const maybe: <V>(of: Kind<V>) => Kind<Maybe<V>>;
156
+ export declare const optional: <V>(c: Cond, value: V) => Maybe<V>;
155
157
  type Fields = Record<string, Kind<unknown>>;
156
158
  export declare const record: <const K extends Fields>(fields: K) => Kind<ValuesOf<K>>;
157
159
  type Cases = Record<string, Fields>;
@@ -161,40 +163,41 @@ type CaseValues<C extends Cases> = {
161
163
  type MatchCases<C extends Cases, T> = {
162
164
  [k in keyof C]: (fields: ValuesOf<C[k]>) => T;
163
165
  };
164
- export declare class Variant<C extends Cases = Cases> {
166
+ export declare class Variant<C extends Cases = Cases, T extends string = 'tag'> {
165
167
  readonly tag: Term;
166
168
  readonly cases: CaseValues<C>;
167
- readonly kind: VariantKind<C>;
168
- constructor(tag: Term, cases: CaseValues<C>, kind: VariantKind<C>);
169
+ readonly kind: VariantKind<C, T>;
170
+ constructor(tag: Term, cases: CaseValues<C>, kind: VariantKind<C, T>);
169
171
  is(k: keyof C & string): Cond;
170
172
  match(cases: MatchCases<C, Cond>): Cond;
171
173
  match(cases: MatchCases<C, TermLike>): Term;
172
174
  match<T>(cases: MatchCases<C, T>): T;
173
175
  }
174
- export type VariantWrite<C extends Cases> = {
176
+ export type VariantWrite<C extends Cases, T extends string = 'tag'> = {
175
177
  [k in keyof C]: {
176
- [f in 'tag' | keyof C[k]]: f extends 'tag' ? k : C[k][f] extends Kind<infer V> ? Writes<V> : never;
178
+ [f in T | keyof C[k]]: f extends T ? k : C[k][f] extends Kind<infer V> ? Writes<V> : never;
177
179
  };
178
180
  }[keyof C];
179
- export declare class VariantKind<C extends Cases> extends Kind<Variant<C>> {
181
+ export declare class VariantKind<C extends Cases, T extends string = 'tag'> extends Kind<Variant<C, T>> {
180
182
  readonly cases: C;
183
+ readonly tagKey: T;
181
184
  readonly names: (keyof C & string)[];
182
- readonly tagKey = "tag";
183
- constructor(cases: C);
185
+ constructor(cases: C, tagKey: T);
184
186
  index(k: string): number;
185
187
  private payload;
186
188
  private filled;
187
189
  private at;
188
190
  vars(name: string): string[];
189
191
  assume(name: string): Cond[];
190
- build(name: string, get: (part: string) => Term): Variant<C>;
191
- make(write: VariantWrite<C>): Variant<C>;
192
+ build(name: string, get: (part: string) => Term): Variant<C, T>;
193
+ invariant(x: Variant<C, T>): Cond[];
194
+ make(write: VariantWrite<C, T>): Variant<C, T>;
192
195
  store(name: string, value: unknown): Record<string, TermLike>;
193
196
  decode(name: string, vars: Record<string, number>): {
194
- tag: keyof C & string;
197
+ [x: string]: (keyof C & string) | Raw;
195
198
  };
196
199
  empty(): {
197
- tag: keyof C & string;
200
+ [x: string]: (keyof C & string) | Raw;
198
201
  };
199
202
  encode(name: string, raw: Raw): {
200
203
  [x: string]: number;
@@ -203,10 +206,13 @@ export declare class VariantKind<C extends Cases> extends Kind<Variant<C>> {
203
206
  [x: string]: number;
204
207
  };
205
208
  sample(rand: Rand, bound: number): {
206
- tag: keyof C & string;
209
+ [x: string]: (keyof C & string) | Raw;
207
210
  };
208
211
  }
209
- export declare const variant: <const C extends Cases>(cases: C) => VariantKind<C>;
212
+ export declare function variant<const C extends Cases>(cases: C): VariantKind<C>;
213
+ export declare function variant<const C extends Cases, const T extends string>(cases: C, opts: {
214
+ tag: T;
215
+ }): VariantKind<C, T>;
210
216
  export declare const intern: ((text: string) => number) & {
211
217
  load(entries: [string, number][]): void;
212
218
  };
@@ -304,6 +310,35 @@ export type ItemsOp = {
304
310
  readonly parts: Record<string, ListOp>;
305
311
  readonly len: TermLike;
306
312
  };
313
+ export type ListFold<V> = {
314
+ from: number;
315
+ step: (acc: Term, item: V) => TermLike;
316
+ };
317
+ export type ListLemma = (acc: Record<string, Term>, i: Term) => Cond;
318
+ export declare class CarriedList<V, Fd extends string = never> {
319
+ readonly name: string;
320
+ readonly kind: Kind<V>;
321
+ readonly len: Term;
322
+ readonly folds: Record<Fd, ListFold<V>>;
323
+ private readonly map;
324
+ readonly lemmas: Record<string, ListLemma>;
325
+ 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>);
327
+ at(i: TermLike): V;
328
+ after(fold: Fd, i: TermLike): Term;
329
+ every(f: (item: V, i: Term) => Cond): Cond;
330
+ some(f: (item: V, i: Term) => Cond): Cond;
331
+ has(x: V): Cond;
332
+ increasing(by: (item: V) => Term): Cond;
333
+ equations(): Cond[];
334
+ private running;
335
+ prove(): Verdict;
336
+ get maps(): string[];
337
+ encode(raw: Raw): {
338
+ maps: Record<string, Map<number, number>>;
339
+ len: number;
340
+ };
341
+ }
307
342
  export declare class ItemsRef<V> {
308
343
  readonly name: string;
309
344
  readonly kind: Kind<V>;
@@ -326,7 +361,7 @@ export declare class ItemsRef<V> {
326
361
  export type Forall = {
327
362
  readonly forall: (key: Term, ...more: Term[]) => Cond;
328
363
  };
329
- export declare function forall(f: (key: Term, key2: Term, key3: Term, key4: Term) => Cond): Forall;
364
+ export declare function forall(f: (key: Term, key2: Term, key3: Term, key4: Term, key5: Term, key6: Term) => Cond): Forall;
330
365
  export type Relation = {
331
366
  readonly when: Cond;
332
367
  readonly holds: Cond;
@@ -356,9 +391,7 @@ type RecurEnv<A extends string, U> = {
356
391
  export type RecurDef<A extends string, U extends Record<string, FnDef>> = {
357
392
  args?: readonly A[];
358
393
  fns?: U;
359
- zero: TermLike | ((env: {
360
- [a in A]: Term;
361
- }) => TermLike);
394
+ zero: TermLike | ((env: RecurEnv<A, U>) => TermLike);
362
395
  next: (x: Term, prev: Term, env: RecurEnv<A, U>) => TermLike;
363
396
  };
364
397
  export declare const recur: <const A extends string = never, U extends Record<string, FnDef> = {}>(name: string, def: RecurDef<A, U>, lemmas?: Record<string, (x: Term, fx: Term, env: RecurEnv<A, U>) => Cond>, between?: Record<string, (x: Term, fx: Term, y: Term, fy: Term, env: RecurEnv<A, U>) => Cond>) => FnDef & {
@@ -369,7 +402,7 @@ type ValuesOf<K extends Kinds> = {
369
402
  [n in keyof K]: K[n] extends Kind<infer V> ? V : never;
370
403
  };
371
404
  type Obj = object;
372
- type Writes<V> = V extends Decimal ? DecimalLike | string : V extends Int ? IntLike : V extends Cond ? Cond | boolean : V extends Choice<infer K> ? Choice<K> | K : V extends Maybe<infer W> ? Maybe<W> | Writes<W> | null : V extends Str ? Str | string : V extends Variant<infer C> ? Variant<C> | VariantWrite<C> : V extends Term ? TermLike : V extends Record<string, unknown> ? {
405
+ type Writes<V> = V extends Decimal ? DecimalLike | string : V extends Int ? IntLike : V extends Cond ? Cond | boolean : V extends Choice<infer K> ? Choice<K> | K : V extends Maybe<infer W> ? Maybe<W> | Writes<W> | null : V extends Str ? Str | string : V extends Variant<infer C, infer T> ? Variant<C, T> | VariantWrite<C, T> : V extends Term ? TermLike : V extends Record<string, unknown> ? {
373
406
  [k in keyof V]: Writes<V[k]>;
374
407
  } : TermLike;
375
408
  type Update<S extends Obj, M extends Obj> = Partial<{
@@ -388,6 +421,7 @@ type Spec = {
388
421
  items?: [string, Kind<unknown>][];
389
422
  kinded?: [string, Kind<unknown>][];
390
423
  carried?: string[];
424
+ lists2?: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
391
425
  fns?: [string, FnDef][];
392
426
  facts?: ((env: never) => Record<string, Cond | Forall>)[];
393
427
  flags: ((env: never) => Record<string, Cond>)[];
@@ -440,6 +474,9 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
440
474
  carries<const N extends string>(...names: N[]): Rule<P, I & {
441
475
  [k in N]: MapRef;
442
476
  }, 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 & {
478
+ [k in N]: CarriedList<V, Fd>;
479
+ }, S, M, F, R, Su, Q>;
443
480
  flags<T extends Record<string, Cond>>(f: (env: P & I & S & M & F & Su & Q) => T): Rule<P, I, S, M, F & {
444
481
  [k in keyof T]: Cond;
445
482
  }, R, Su, Q>;
@@ -458,6 +495,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
458
495
  prune(order: keyof R & string, after: (p: P) => TermLike): Rule<P, I, S, M, F, R, Su, Q>;
459
496
  law(name: string, f: (env: P & S & M & Su) => Cond | Forall): Law;
460
497
  private lawOf;
498
+ kindLaws(): Law[];
461
499
  stepLaw(name: string, then: (before: P & I & S & M & F & Su & Q, after: S & M & Su) => Cond | Forall, when?: (before: P & I & S & M & F & Su & Q) => Cond): StepLaw;
462
500
  stepsLaw(name: string, k: number, then: (first: P & I & S & M & F & Su & Q, last: S & M & Su, requests: (I & Q)[]) => Cond | Forall, when?: (before: P & I & S & M & F & Su & Q, j: number) => Cond | undefined): StepLaw;
463
501
  leadsTo(name: string, spec: {
@@ -511,7 +549,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
511
549
  [k in keyof P]: Raw;
512
550
  }): Record<string, number>;
513
551
  encodeInput(values: {
514
- [k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : Raw;
552
+ [k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : I[k] extends CarriedList<any, any> ? readonly Raw[] : Raw;
515
553
  }): Request;
516
554
  randomParams(rand: Rand, bound: number): Record<string, number>;
517
555
  randomInput(rand: Rand, bound: number): Request;
@@ -552,18 +590,18 @@ export type Decision = {
552
590
  then: C;
553
591
  goal: Goal;
554
592
  };
555
- type ResultOf<V> = 0 extends 1 & V ? Term : [V] extends [Cond] ? Cond : [V] extends [Decimal] ? Decimal : [V] extends [Int] ? Int : [V] extends [Choice<infer K>] ? Choice<K> : [V] extends [Str] ? Str : [V] extends [Variant<infer C>] ? Variant<C> : [V] extends [Term | number] ? Term : [V] extends [Record<string, unknown>] ? {
593
+ type ResultOf<V> = 0 extends 1 & V ? Term : [V] extends [Cond] ? Cond : [V] extends [Decimal] ? Decimal : [V] extends [Int] ? Int : [V] extends [Choice<infer K>] ? Choice<K> : [V] extends [Str] ? Str : [V] extends [Variant<infer C, infer T>] ? Variant<C, T> : [V] extends [Maybe<infer W>] ? Maybe<ResultOf<W>> : [V] extends [Term | number] ? Term : [V] extends [Record<string, unknown>] ? {
556
594
  [f in keyof V]: ResultOf<V[f]>;
557
595
  } : Term;
558
596
  type Result<T> = {
559
597
  [k in keyof T]: ResultOf<T[k]>;
560
598
  };
561
- type ValueOf<V> = 0 extends 1 & V ? number : [V] extends [Cond] ? boolean : [V] extends [Choice<infer K>] ? K : [V] extends [Str] ? string : [V] extends [Variant<infer C>] ? CaseRaw<C> : [V] extends [Term | number | Int | Decimal] ? number : [V] extends [Record<string, unknown>] ? {
599
+ type ValueOf<V> = 0 extends 1 & V ? number : [V] extends [Cond] ? boolean : [V] extends [Choice<infer K>] ? K : [V] extends [Str] ? string : [V] extends [Variant<infer C, infer T>] ? CaseRaw<C, T> : [V] extends [Maybe<infer W>] ? ValueOf<W> | null : [V] extends [Term | number | Int | Decimal] ? number : [V] extends [Record<string, unknown>] ? {
562
600
  [f in keyof V]: ValueOf<V[f]>;
563
601
  } : number;
564
- type CaseRaw<C extends Cases> = {
602
+ type CaseRaw<C extends Cases, T extends string = 'tag'> = {
565
603
  [k in keyof C]: {
566
- tag: k;
604
+ [t in T]: k;
567
605
  } & {
568
606
  [f in keyof C[k]]: C[k][f] extends Kind<infer V> ? ValueOf<V> : never;
569
607
  };
@@ -571,7 +609,7 @@ type CaseRaw<C extends Cases> = {
571
609
  type Value<T> = {
572
610
  [k in keyof T]: ValueOf<T[k]>;
573
611
  };
574
- type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any> | {
612
+ type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any, any> | Maybe<any> | {
575
613
  [f: string]: Field | number;
576
614
  };
577
615
  export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj = {}> {
@@ -685,3 +723,55 @@ export declare function crossCheckDecision(d: Decision, opts?: {
685
723
  failure?: string;
686
724
  };
687
725
  export declare const proveLoop: (l: LoopRule<Obj, Obj, Obj>, laws: Law[]) => Verdict;
726
+ export type TreeValue<I> = {
727
+ leaf: I;
728
+ } | {
729
+ node: I;
730
+ left: TreeValue<I>;
731
+ right: TreeValue<I>;
732
+ };
733
+ type FoldSpec = {
734
+ name: string;
735
+ params: [string, Kind<unknown>][];
736
+ inputs: [string, Kind<unknown>][];
737
+ pre: ((p: never) => Cond)[];
738
+ assume: ((env: never) => Cond)[];
739
+ leaf?: (env: never) => Record<string, TermLike>;
740
+ node?: (env: never, left: never, right: never) => Record<string, TermLike>;
741
+ };
742
+ export declare class FoldRule<P extends Obj = {}, I extends Obj = {}, S extends Obj = {}> {
743
+ private readonly spec;
744
+ constructor(spec: FoldSpec);
745
+ private with;
746
+ get name(): string;
747
+ params<K extends Kinds>(kinds: K): FoldRule<P & ValuesOf<K>, I, S>;
748
+ inputs<K extends Kinds>(kinds: K): FoldRule<P, I & ValuesOf<K>, S>;
749
+ pre(f: (p: P) => Cond): FoldRule<P, I, S>;
750
+ assume(f: (env: P & I) => Cond): FoldRule<P, I, S>;
751
+ leaf<T extends Record<string, TermLike>>(f: (env: P & I) => T): FoldRule<P, I, {
752
+ [k in keyof T]: Term;
753
+ }>;
754
+ node(f: (env: P & I, left: S, right: S) => {
755
+ [k in keyof S]: TermLike;
756
+ }): FoldRule<P, I, S>;
757
+ private env;
758
+ private names;
759
+ private side;
760
+ law(name: string, f: (env: P & S) => Cond): Law;
761
+ get fold(): Fold;
762
+ run(tree: TreeValue<{
763
+ [k in keyof I]: Raw;
764
+ }>, params?: {
765
+ [k in keyof P]?: Raw;
766
+ }): {
767
+ [k in keyof S]: number;
768
+ };
769
+ crossCheck(laws?: Law[], opts?: {
770
+ trials?: number;
771
+ depth?: number;
772
+ bound?: number;
773
+ rand?: Rand;
774
+ }): Agreement;
775
+ }
776
+ export declare const fold: (name: string) => FoldRule;
777
+ export declare const proveFold: (f: FoldRule<Obj, Obj, Obj>, laws: Law[]) => Verdict;