gdp-wired 0.6.0 → 0.8.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
@@ -1,4 +1,7 @@
1
- <img src="https://raw.githubusercontent.com/monotykamary/gdp-wired/main/static/banner-ink.svg" alt="gdp-wired" width="100%" />
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/monotykamary/gdp-wired/main/static/banner-dark.svg">
3
+ <img src="https://raw.githubusercontent.com/monotykamary/gdp-wired/main/static/banner-light.svg" alt="gdp-wired: an automatic prover for stateful rules" width="100%">
4
+ </picture>
2
5
 
3
6
  # gdp-wired
4
7
 
@@ -27,8 +30,10 @@ proveRule(budget, [bounded]) // {proven: true, checked: true}
27
30
  Names are typed: `({s, bb}) => …` or a law that reads the request `c` is a TypeScript error, not a runtime surprise.
28
31
  Forget the cap (`s.plus(c)`) and the kernel refuses the proof; a search from the start turns it into a real run:
29
32
  `{params: {b: 1, m: 0}, trace: [{c: 2}]}`. `bun examples/budget.ts` runs all of it, including the generated JavaScript,
30
- and `examples/domains/` holds 92 rules from nine domains (finance, access control, public services, operations,
31
- protocols, commerce, supply chains, games, education), each checked three ways.
33
+ and `examples/domains/` holds 187 rules from 21 domains (finance, access control, public services, operations,
34
+ protocols, commerce, supply chains, games, education, healthcare, energy, networking, AI agents, cloud, ad tech,
35
+ governance, insurance, bookings, safety and IoT, mobility, media), each checked three ways: the kernel proves every law
36
+ and refutes every broken variant, random runs of both runtimes agree, and Lean checks the trees.
32
37
 
33
38
  ## Where it fits
34
39
 
@@ -56,6 +61,12 @@ All of these are proven automatically, and each proof is also a Lean theorem:
56
61
  - **Refinement**: an implementation behaves like a simpler spec, step for step (`examples/composition.ts`).
57
62
  - **Functions**: division, remainder, and your own functions, known by their facts; a function defined in Lean brings
58
63
  facts proven there (`examples/functions.ts`).
64
+ - **Lemmas you state**: a function defined by a recurrence (`recur`), of one argument or several, reading other
65
+ functions, and lemmas about it proven by induction, at one point or between two, which every rule that reads it may
66
+ then use: a backoff delay is at most 64 at every attempt and never shrinks (`examples/lemmas.ts`).
67
+ - **Records and variants**: inputs, state, results, map cells and list items made of named fields, or one of several
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
+ (`examples/datatypes.ts`).
59
70
  - **Pure functions and two-run laws**: laws about what a function returns, and laws comparing two calls, like "the
60
71
  secret never changes the answer". `examples/jev-fabric/` ports a Bend project's policy core and proves all 59 laws its
61
72
  Bend proofs state.
@@ -66,7 +77,7 @@ synthesizer finds one and the kernel proves it.
66
77
  **What it leaves to Lean or Bend.** Two things, and neither is a gap the next feature closes: proofs the prover would
67
78
  have to invent (induction over a recursive structure whose proof needs a lemma nobody stated, and arithmetic with
68
79
  products of unknowns beyond multiplying two known facts; integer arithmetic with induction or with products is
69
- undecidable), and shipping an arbitrary algorithm as its proved source (gdp-wired ships the rule as JavaScript; Bend
80
+ undecidable; a lemma you state about a recurrence is proven, but a recursive datatype as a value is Lean's), and shipping an arbitrary algorithm as its proved source (gdp-wired ships the rule as JavaScript; Bend
70
81
  compiles the proved source itself). The full list of what is and isn't in scope is in
71
82
  [`docs/comparison.md`](docs/comparison.md#gdp-wireds-scope-in-detail).
72
83
 
@@ -101,13 +112,46 @@ The untrusted parts are held to the trusted ones by the test suite:
101
112
  tests, and the few that survive are equivalent (`kernel/MUTANTS.md`).
102
113
  - **Fuzz**: the kernel never accepts a refutation of something that has a model.
103
114
 
115
+ ## With gdp-ts
116
+
117
+ [gdp-ts](https://github.com/rauchg/gdp-ts) makes every call site present a proof that a check ran on exactly these
118
+ values, and trusts the module that performs the check. `gdp-wired/gdp` proves that check: the trusted module hands its
119
+ private prover to a `guard` (a function's condition) or a `gate` (a rule's flag), which proves the laws when the module
120
+ loads, refuses to load if one fails, and mints a proof only where the proven condition holds. gdp-ts then holds every
121
+ call site to it: the policy is proven, every caller is held to it, and Lean checks the proof again.
122
+
123
+ ```ts
124
+ // proofs/can-set-protection.ts (trusted: the only module that can mint this proof)
125
+ import {defineProof, type Named, type Proof} from '@gdp-ts/core'
126
+ import {fun, no, oneOf} from 'gdp-wired'
127
+ import {guard} from 'gdp-wired/gdp'
128
+
129
+ const canSet = fun('can set password protection')
130
+ .inputs({role: oneOf('owner', 'member', 'viewer', 'none'), plan: oneOf('free', 'pro', 'enterprise')})
131
+ .returns(({role, plan}) => ({allow: role.is('owner').or(role.is('member')).and(plan.is('free').not())}))
132
+ const laws = [canSet.law('the free plan never has protection', ({plan}, out) => [plan.is('free'), out.allow], () => no)]
133
+
134
+ const CanSetProtection = defineProof('CanSetProtection') // not exported
135
+ export interface CanSetProtection<U, P> extends Proof<'CanSetProtection', [U, P]> {}
136
+ const mint = guard(CanSetProtection, canSet, 'allow', {laws}) // proven here, or the module doesn't load
137
+
138
+ export async function canSetProtection<U, P>(user: Named<U, UserId>, project: Named<P, ProjectId>): Promise<CanSetProtection<U, P> | null> {
139
+ return mint({role: await db.role(user.value, project.value), plan: await db.plan(project.value)}, user, project)
140
+ }
141
+ ```
142
+
143
+ A `gate` does the same for a rule with state: each request takes the rule's step, and a proof is minted when the step
144
+ takes the flag (a one-time token redeemed, a sign-in while the account is unlocked, an email within the hourly cap).
145
+ Requests outside what was proven (a failed `assume`, a history read out of order) mint nothing and leave the state as it
146
+ was. `examples/gdp.ts` runs gdp-ts's own example this way; `@gdp-ts/core` is an optional peer dependency.
147
+
104
148
  ## Writing laws
105
149
 
106
150
  The builders (`core/dsl.ts`) hand out typed values for every name you declare; everything compiles to the plain machine
107
151
  the kernel checks, so nothing in them is trusted.
108
152
 
109
153
  ```ts
110
- import {bool, count, forall, maybe, nat, oneOf, proveSteps, relation, rule, yes} from 'gdp-wired'
154
+ import {bool, count, forall, fun, maybe, min, nat, no, oneOf, proveSteps, record, recur, relation, rule, variant, when, yes} from 'gdp-wired'
111
155
 
112
156
  // Maps and flags: a grant is decided once per step; each map is written once; a map law holds at every key.
113
157
  const caps = rule('caps')
@@ -142,12 +186,36 @@ const window = rule('window limit')
142
186
  const trial = rule('trial').inputs({plan: oneOf('free', 'pro'), spent: maybe(nat), allowance: maybe(nat)})
143
187
  const failsClosed = trial.decide('a missing fact means no trial', ({spent}) => spent.isNone,
144
188
  ({plan, spent, allowance}) => plan.match({free: () => spent.match({none: () => yes, some: s => allowance.match({none: () => yes, some: a => s.atLeast(a)})}), pro: () => yes}))
189
+
190
+ // Records and variants: a payment is by card or in cash, each with fields of its own, read only in `match`.
191
+ const Pay = variant({card: {amount: nat, verified: bool}, cash: {amount: nat}})
192
+ const approve = fun('approve').inputs({pay: Pay, who: record({limit: nat, blocked: bool})})
193
+ .returns(({pay, who}) => ({ok: who.blocked.not().and(pay.match({card: c => c.verified.and(c.amount.atMost(who.limit)), cash: c => c.amount.atMost(200)}))}))
194
+ const unverified = approve.law('an unverified card is never approved', ({pay}, out) => [pay.match({card: c => c.verified.not(), cash: () => no}), out.ok], () => no)
195
+
196
+ // A lemma you state, proven by induction: the backoff delay is defined by its recurrence, and its lemmas are facts
197
+ // every rule that reads it may use (its two equations alone don't bound it at an unknown attempt).
198
+ const delay = recur('delay', {zero: 1, next: (_, d) => min(d.times(2), 64)}, {'at least 1': (_, d) => d.atLeast(1), 'at most 64': (_, d) => d.atMost(64)})
199
+ const retry = rule('retry').inputs({failed: bool, now: nat}).state({tries: 0, next: 0, last: 0}).fns({delay})
200
+ .assume(({now, last}) => now.atLeast(last))
201
+ .step(({failed, now, tries, next, delay}) => ({tries: when(failed, tries.plus(1), 0), next: when(failed.and(now.atLeast(next)), now.plus(delay(tries)), next), last: now}))
202
+ const soon = retry.law('the next try is at most 64 after the last request', ({next, last}) => next.atMost(last.plus(64)))
145
203
  ```
146
204
 
147
205
 
148
- - **Kinds**: `nat`, `upTo(k)`, `bool`, `oneOf(...)`, `maybe(kind)`, `int`, `decimal(places)`, `str({like, maxLength})`;
149
- their assumptions (a bit is 0 or 1, a choice is in range) go to the kernel by themselves, and
150
- `encodeInput`/`encodeParams` turn runtime values into the numbers the machine reads.
206
+ - **Kinds**: `nat`, `upTo(k)`, `bool`, `oneOf(...)`, `maybe(kind)`, `int`, `decimal(places)`, `str({like, maxLength})`,
207
+ `record({...})`, `variant({...})`; their assumptions (a bit is 0 or 1, a choice is in range) go to the kernel by
208
+ themselves, and `encodeInput`/`encodeParams` turn runtime values into the numbers the machine reads.
209
+ - **Records and variants**: `record({limit: nat, blocked: bool})` hands out an object of its fields' values;
210
+ `variant({card: {...}, cash: {...}})` a value with `is(case)` and `match({card: c => ..., cash: c => ...})`, the only
211
+ 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'
214
+ variables underneath (`pay.card.amount`, and the case's index as `pay`), so the kernel, both runtimes and the Lean
215
+ twin see nothing new. Maps and lists of them: `.maps({Acct: record({...}).from({...})})` (`Acct.at(k)`,
216
+ `Acct.set(k, value)`, `Acct.each(k => value)`) and `.items({Queue: Job})` (`push`, `pop`, `set`, `insertAt`,
217
+ `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).
151
219
  - **State of any kind**: `.state({open: bool.from(true), status: oneOf('new', 'done').from('new'), owner: str().from(''), note: maybe(nat).from(null)})`;
152
220
  a step writes each kind's own values, `when(c, a, b)` chooses between any two values of a kind, and `rule.decode(state)`
153
221
  reads them back as runtime values.
@@ -168,6 +236,16 @@ const failsClosed = trial.decide('a missing fact means no trial', ({spent}) => s
168
236
  take arguments, `perMinute(t)`; the kernel knows each only by its facts (`.facts(...)`, and a library function's
169
237
  own), and the Lean export takes them as the hypothesis they are, proving them where the function has a Lean
170
238
  definition. A function defined and proven in Lean is how a rule reaches past what the kernel decides: `examples/functions.ts`.
239
+ - **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
242
+ and f(x), proven when the function is made, all of them together by induction (the recurrence run as a machine, each
243
+ 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
+ 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
+ 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`).
171
249
  - **Liveness** (`rule.leadsTo(name, {from, to, rank, helpful})`, `proveLeadsTo`): whenever `from` holds, `to` holds later in
172
250
  every run that takes each helpful request infinitely often; proven by a rank no step raises while waiting and each
173
251
  helpful request lowers in its region. `rank` may return several terms (compared in order) and `helpful` may be a list
@@ -230,7 +308,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
230
308
  | `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 |
231
309
  | `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
232
310
  | `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
233
- | `examples/` | The builders at work: `domains/` (92 rules across nine domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `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) |
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) |
234
312
  | `docs/` | The comparison with Bend and Lean, and the full scope |
235
313
  | `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
236
314
  | `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/`) |
@@ -266,10 +344,24 @@ modal run modal/run.py --jobs modal/suite.json --out runs/modal/suite && sh moda
266
344
 
267
345
  Each directory's README has its own runs and results.
268
346
 
347
+ ## The skill
348
+
349
+ `npx skills add monotykamary/gdp-wired` installs a skill that guides coding agents (and people) through writing a rule
350
+ the way the code does it, stating its laws, reading a refused proof and handing a claim to Lean:
351
+
352
+ - [**Workflow**](skills/gdp-wired/SKILL.md#workflow): the checklist for adding or changing a rule whose invariants matter.
353
+ - [**Writing rules**](skills/gdp-wired/references/writing-rules.md): kinds, maps, step and k-step laws, histories,
354
+ liveness, decisions, two-run laws, loops.
355
+ - [**Reading verdicts**](skills/gdp-wired/references/reading-verdicts.md): a bug or a missing invariant, and what to
356
+ change.
357
+ - [**Shipping**](skills/gdp-wired/references/shipping.md): running the proven rule, and holding existing code to it.
358
+ - [**The Lean handoff**](skills/gdp-wired/references/lean-handoff.md): exporting the proofs, and functions proven in Lean.
359
+ - [**Where it fits**](skills/gdp-wired/references/where-it-fits.md): gdp-wired, Lean or Bend.
360
+
269
361
  ## Credits
270
362
 
271
363
  [gdp-ts](https://github.com/rauchg/gdp-ts) by Guillermo Rauch (Ghosts of Departed Proofs for TypeScript) is where the
272
- audit path starts; [Handwired](https://github.com/chenglou/handwired) by Cheng Lou is the circuit and GPU runtime this
364
+ audit path starts, and what `gdp-wired/gdp` mints proofs for; [Handwired](https://github.com/chenglou/handwired) by Cheng Lou is the circuit and GPU runtime this
273
365
  builds on, through a fork; [Bend](https://github.com/bendlang/bend) is the language the laws were first written in,
274
366
  and the yardstick for the benchmarks.
275
367
 
@@ -1,4 +1,4 @@
1
- import { type C, type E, type Law, type Machine, type Request, type State } from './machine.js';
1
+ import { type C, type E, type Law, type Recurrence, type Machine, type Request, type State } from './machine.js';
2
2
  import { 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;
@@ -43,11 +43,17 @@ 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>;
47
+ export declare function when<T extends Record<string, unknown>>(c: Cond, then: T, otherwise: T): T;
46
48
  export declare const choose: (c: Cond, then: Cond, otherwise: Cond) => Cond;
49
+ export declare function same(a: unknown, b: unknown): Cond;
47
50
  export declare const count: (c: Cond) => Term;
48
51
  export declare const sum: (first: TermLike, ...rest: TermLike[]) => Term;
49
52
  type Rand = (k: number) => number;
50
- type Raw = number | boolean | string | null | undefined;
53
+ export type Raw = number | boolean | string | null | undefined | RawObject | readonly Raw[];
54
+ export type RawObject = {
55
+ readonly [field: string]: Raw;
56
+ };
51
57
  export type At = string | ((name: string) => string);
52
58
  export type Init<V> = {
53
59
  readonly kind: Kind<V>;
@@ -57,7 +63,8 @@ export declare abstract class Kind<V> {
57
63
  readonly _value: V;
58
64
  abstract vars(name: string): string[];
59
65
  abstract assume(name: string): Cond[];
60
- abstract value(name: string, at?: At): V;
66
+ abstract build(name: string, get: (part: string) => Term): V;
67
+ value(name: string, at?: At): V;
61
68
  abstract encode(name: string, raw: Raw): Record<string, number>;
62
69
  abstract random(name: string, rand: Rand, bound: number): Record<string, number>;
63
70
  abstract store(name: string, value: unknown): Record<string, TermLike>;
@@ -145,6 +152,61 @@ export declare class Maybe<V> {
145
152
  }): Term;
146
153
  }
147
154
  export declare const maybe: <V>(of: Kind<V>) => Kind<Maybe<V>>;
155
+ type Fields = Record<string, Kind<unknown>>;
156
+ export declare const record: <const K extends Fields>(fields: K) => Kind<ValuesOf<K>>;
157
+ type Cases = Record<string, Fields>;
158
+ type CaseValues<C extends Cases> = {
159
+ [k in keyof C]: ValuesOf<C[k]>;
160
+ };
161
+ type MatchCases<C extends Cases, T> = {
162
+ [k in keyof C]: (fields: ValuesOf<C[k]>) => T;
163
+ };
164
+ export declare class Variant<C extends Cases = Cases> {
165
+ readonly tag: Term;
166
+ readonly cases: CaseValues<C>;
167
+ readonly kind: VariantKind<C>;
168
+ constructor(tag: Term, cases: CaseValues<C>, kind: VariantKind<C>);
169
+ is(k: keyof C & string): Cond;
170
+ match(cases: MatchCases<C, Cond>): Cond;
171
+ match(cases: MatchCases<C, TermLike>): Term;
172
+ match<T>(cases: MatchCases<C, T>): T;
173
+ }
174
+ export type VariantWrite<C extends Cases> = {
175
+ [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;
177
+ };
178
+ }[keyof C];
179
+ export declare class VariantKind<C extends Cases> extends Kind<Variant<C>> {
180
+ readonly cases: C;
181
+ readonly names: (keyof C & string)[];
182
+ readonly tagKey = "tag";
183
+ constructor(cases: C);
184
+ index(k: string): number;
185
+ private payload;
186
+ private filled;
187
+ private at;
188
+ vars(name: string): string[];
189
+ assume(name: string): Cond[];
190
+ build(name: string, get: (part: string) => Term): Variant<C>;
191
+ make(write: VariantWrite<C>): Variant<C>;
192
+ store(name: string, value: unknown): Record<string, TermLike>;
193
+ decode(name: string, vars: Record<string, number>): {
194
+ tag: keyof C & string;
195
+ };
196
+ empty(): {
197
+ tag: keyof C & string;
198
+ };
199
+ encode(name: string, raw: Raw): {
200
+ [x: string]: number;
201
+ };
202
+ random(name: string, rand: Rand, bound: number): {
203
+ [x: string]: number;
204
+ };
205
+ sample(rand: Rand, bound: number): {
206
+ tag: keyof C & string;
207
+ };
208
+ }
209
+ export declare const variant: <const C extends Cases>(cases: C) => VariantKind<C>;
148
210
  export declare const intern: ((text: string) => number) & {
149
211
  load(entries: [string, number][]): void;
150
212
  };
@@ -225,6 +287,42 @@ export declare class ListRef {
225
287
  within(): Forall;
226
288
  increasing(): Forall;
227
289
  }
290
+ export type PartsWrite = {
291
+ readonly parts: Record<string, MapWrite>;
292
+ };
293
+ export declare class CellsRef<V> {
294
+ readonly name: string;
295
+ readonly kind: Kind<V>;
296
+ private readonly map;
297
+ constructor(name: string, kind: Kind<V>, map: (part: string) => MapRef);
298
+ at(key: TermLike): V;
299
+ set(key: TermLike, value: Writes<V>): PartsWrite;
300
+ each(f: (key: Term) => Writes<V>): PartsWrite;
301
+ }
302
+ export type ItemsOp = {
303
+ readonly when: Cond;
304
+ readonly parts: Record<string, ListOp>;
305
+ readonly len: TermLike;
306
+ };
307
+ export declare class ItemsRef<V> {
308
+ readonly name: string;
309
+ readonly kind: Kind<V>;
310
+ readonly len: Term;
311
+ private readonly list;
312
+ constructor(name: string, kind: Kind<V>, len: Term, list: (part: string) => ListRef);
313
+ private get names();
314
+ at(i: TermLike): V;
315
+ has(x: V): Cond;
316
+ private op;
317
+ push(x: Writes<V>, when?: Cond): ItemsOp;
318
+ pop(when?: Cond): ItemsOp;
319
+ set(i: TermLike, x: Writes<V>, when?: Cond): ItemsOp;
320
+ insertAt(i: TermLike, x: Writes<V>, when?: Cond): ItemsOp;
321
+ removeAt(i: TermLike, when?: Cond): ItemsOp;
322
+ apply(...ops: ItemsOp[]): Record<string, TermLike | PartsWrite>;
323
+ within(): Forall;
324
+ increasing(by: (x: V) => Term): Forall;
325
+ }
228
326
  export type Forall = {
229
327
  readonly forall: (key: Term, ...more: Term[]) => Cond;
230
328
  };
@@ -239,7 +337,9 @@ export type FnDef = {
239
337
  arity: number;
240
338
  js: string | ((...args: number[]) => number);
241
339
  lean?: string;
242
- facts?: (f: (...args: TermLike[]) => Term) => Record<string, Cond | Forall>;
340
+ facts?: (f: (...args: TermLike[]) => Term, uses?: Record<string, (...args: TermLike[]) => Term>) => Record<string, Cond | Forall>;
341
+ recur?: Recurrence;
342
+ uses?: Record<string, FnDef>;
243
343
  };
244
344
  export declare const fn: (arity: number, js: FnDef["js"], opts?: {
245
345
  lean?: string;
@@ -247,16 +347,35 @@ export declare const fn: (arity: number, js: FnDef["js"], opts?: {
247
347
  }) => FnDef;
248
348
  export declare const divBy: (c: number) => FnDef;
249
349
  export declare const modBy: (c: number) => FnDef;
350
+ type FnTerm = (...args: TermLike[]) => Term;
351
+ type RecurEnv<A extends string, U> = {
352
+ [a in A]: Term;
353
+ } & {
354
+ [u in keyof U]: FnTerm;
355
+ };
356
+ export type RecurDef<A extends string, U extends Record<string, FnDef>> = {
357
+ args?: readonly A[];
358
+ fns?: U;
359
+ zero: TermLike | ((env: {
360
+ [a in A]: Term;
361
+ }) => TermLike);
362
+ next: (x: Term, prev: Term, env: RecurEnv<A, U>) => TermLike;
363
+ };
364
+ 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 & {
365
+ verdict: Verdict;
366
+ };
250
367
  type Kinds = Record<string, Kind<unknown>>;
251
368
  type ValuesOf<K extends Kinds> = {
252
369
  [n in keyof K]: K[n] extends Kind<infer V> ? V : never;
253
370
  };
254
371
  type Obj = object;
255
- 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 : TermLike;
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> ? {
373
+ [k in keyof V]: Writes<V[k]>;
374
+ } : TermLike;
256
375
  type Update<S extends Obj, M extends Obj> = Partial<{
257
376
  [k in keyof S]: Writes<S[k]>;
258
377
  } & {
259
- [k in keyof M]: MapWrite;
378
+ [k in keyof M]: M[k] extends CellsRef<unknown> | ItemsRef<unknown> ? PartsWrite : MapWrite;
260
379
  }>;
261
380
  type Spec = {
262
381
  name: string;
@@ -265,6 +384,8 @@ type Spec = {
265
384
  state: [string, number | ((p: never) => TermLike)][];
266
385
  maps: [string, number][];
267
386
  lists?: string[];
387
+ cells?: [string, Kind<unknown>][];
388
+ items?: [string, Kind<unknown>][];
268
389
  kinded?: [string, Kind<unknown>][];
269
390
  carried?: string[];
270
391
  fns?: [string, FnDef][];
@@ -293,8 +414,8 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
293
414
  state<T extends Record<string, number | ((p: P) => TermLike) | Init<unknown>>>(init: T): Rule<P, I, S & {
294
415
  [k in keyof T]: T[k] extends Init<infer V> ? V : Term;
295
416
  }, M, F, R, Su, Q>;
296
- maps<T extends Record<string, number>>(init: T): Rule<P, I, S, M & {
297
- [k in keyof T]: MapRef;
417
+ maps<T extends Record<string, number | Init<unknown>>>(init: T): Rule<P, I, S, M & {
418
+ [k in keyof T]: T[k] extends Init<infer V> ? CellsRef<V> : MapRef;
298
419
  }, F, R, Su, Q>;
299
420
  ints<T extends Record<string, number | ((p: P) => IntLike)>>(init: T): Rule<P, I, S & {
300
421
  [k in keyof T]: Int;
@@ -307,6 +428,11 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
307
428
  }, M & {
308
429
  [k in N]: ListRef;
309
430
  }, F, R, Su, Q>;
431
+ items<T extends Record<string, Kind<unknown>>>(kinds: T): Rule<P, I, S & {
432
+ [k in keyof T & string as `${k}_len`]: Term;
433
+ }, M & {
434
+ [k in keyof T]: ItemsRef<T[k] extends Kind<infer V> ? V : never>;
435
+ }, F, R, Su, Q>;
310
436
  fns<T extends Record<string, FnDef>>(defs: T): Rule<P & {
311
437
  [k in keyof T]: (...args: TermLike[]) => Term;
312
438
  }, I, S, M, F, R, Su, Q>;
@@ -348,6 +474,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
348
474
  private kindAssumptions;
349
475
  private afterEnv;
350
476
  private env;
477
+ private composites;
351
478
  private names;
352
479
  private whatIs;
353
480
  private distinct;
@@ -363,7 +490,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
363
490
  };
364
491
  private verdict;
365
492
  private crossStep;
366
- compare<St extends Record<string, number>>(reference: {
493
+ compare<St extends Record<string, Raw>>(reference: {
367
494
  init: (params: {
368
495
  [k in keyof P]: Raw;
369
496
  }) => St;
@@ -425,13 +552,28 @@ export type Decision = {
425
552
  then: C;
426
553
  goal: Goal;
427
554
  };
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>] ? {
556
+ [f in keyof V]: ResultOf<V[f]>;
557
+ } : Term;
428
558
  type Result<T> = {
429
- [k in keyof T]: T[k] extends Cond ? Cond : T[k] extends Decimal ? Decimal : T[k] extends Int ? Int : T[k] extends Choice<infer K> ? Choice<K> : T[k] extends Str ? Str : Term;
559
+ [k in keyof T]: ResultOf<T[k]>;
430
560
  };
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>] ? {
562
+ [f in keyof V]: ValueOf<V[f]>;
563
+ } : number;
564
+ type CaseRaw<C extends Cases> = {
565
+ [k in keyof C]: {
566
+ tag: k;
567
+ } & {
568
+ [f in keyof C[k]]: C[k][f] extends Kind<infer V> ? ValueOf<V> : never;
569
+ };
570
+ }[keyof C];
431
571
  type Value<T> = {
432
- [k in keyof T]: T[k] extends Cond ? boolean : T[k] extends Choice<infer K> ? K : T[k] extends Str ? string : number;
572
+ [k in keyof T]: ValueOf<T[k]>;
573
+ };
574
+ type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any> | {
575
+ [f: string]: Field | number;
433
576
  };
434
- type Field = Term | Cond | Int | Decimal | Choice<string> | Str;
435
577
  export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj = {}> {
436
578
  private readonly r;
437
579
  private readonly out?;
@@ -469,6 +611,7 @@ export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj =
469
611
  }): Value<O>;
470
612
  }
471
613
  export declare const fun: (name: string) => Fun;
614
+ export declare const funOf: (d: Decision) => Fun<Obj, Obj, Obj> | undefined;
472
615
  export type Agreement = {
473
616
  checked: number;
474
617
  failure?: string;
@@ -523,6 +666,10 @@ export type Verdict = {
523
666
  why?: string;
524
667
  trees?: Record<string, Tree>;
525
668
  };
669
+ export declare class Unproven extends Error {
670
+ readonly verdict: Verdict;
671
+ constructor(what: string, verdict: Verdict);
672
+ }
526
673
  export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[]): Verdict;
527
674
  export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[]): Verdict;
528
675
  export type { StepLaw };