gdp-wired 0.8.0 → 0.9.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
@@ -209,8 +209,11 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
209
209
  - **Records and variants**: `record({limit: nat, blocked: bool})` hands out an object of its fields' values;
210
210
  `variant({card: {...}, cash: {...}})` a value with `is(case)` and `match({card: c => ..., cash: c => ...})`, the only
211
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'
212
+ `when`, and `same(a, b)` compares two values of one kind part by part; `optional(c, value)` is a value present where c
213
+ holds (a result that may be missing). At runtime a record is an object of its fields and a variant `{tag: 'card',
214
+ ...fields}` (or the key given, `variant({...}, {tag: 'kind'})`); a field or case the kind doesn't declare is refused.
215
+ 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()`,
216
+ which proveRule, proveSteps and proveLeadsTo prove beside the laws they are given. Both are their fields'
214
217
  variables underneath (`pay.card.amount`, and the case's index as `pay`), so the kernel, both runtimes and the Lean
215
218
  twin see nothing new. Maps and lists of them: `.maps({Acct: record({...}).from({...})})` (`Acct.at(k)`,
216
219
  `Acct.set(k, value)`, `Acct.each(k => value)`) and `.items({Queue: Job})` (`push`, `pop`, `set`, `insertAt`,
@@ -237,15 +240,17 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
237
240
  own), and the Lean export takes them as the hypothesis they are, proving them where the function has a Lean
238
241
  definition. A function defined and proven in Lean is how a rule reaches past what the kernel decides: `examples/functions.ts`.
239
242
  - **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
243
+ f(x + 1, a..) = next(x, f(x, a..), a..), the further arguments `args` (up to four) fixed along the recursion, each term
244
+ 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
245
  and f(x), proven when the function is made, all of them together by induction (the recurrence run as a machine, each
243
246
  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
247
  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
248
  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`).
249
+ between are the function's facts. The runtimes compute f by the recurrence, continuing from the last value computed at
250
+ the same further arguments (a rule that reads f at a growing count takes one step a request); the Lean export defines
251
+ `Recur.<name>` by it (a function of several arguments through an injective pairing, proven so in Lean) and checks the
252
+ 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
253
+ 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
254
  - **Liveness** (`rule.leadsTo(name, {from, to, rank, helpful})`, `proveLeadsTo`): whenever `from` holds, `to` holds later in
250
255
  every run that takes each helpful request infinitely often; proven by a rank no step raises while waiting and each
251
256
  helpful request lowers in its region. `rank` may return several terms (compared in order) and `helpful` may be a list
@@ -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
  };
@@ -326,7 +332,7 @@ export declare class ItemsRef<V> {
326
332
  export type Forall = {
327
333
  readonly forall: (key: Term, ...more: Term[]) => Cond;
328
334
  };
329
- export declare function forall(f: (key: Term, key2: Term, key3: Term, key4: Term) => Cond): Forall;
335
+ export declare function forall(f: (key: Term, key2: Term, key3: Term, key4: Term, key5: Term, key6: Term) => Cond): Forall;
330
336
  export type Relation = {
331
337
  readonly when: Cond;
332
338
  readonly holds: Cond;
@@ -356,9 +362,7 @@ type RecurEnv<A extends string, U> = {
356
362
  export type RecurDef<A extends string, U extends Record<string, FnDef>> = {
357
363
  args?: readonly A[];
358
364
  fns?: U;
359
- zero: TermLike | ((env: {
360
- [a in A]: Term;
361
- }) => TermLike);
365
+ zero: TermLike | ((env: RecurEnv<A, U>) => TermLike);
362
366
  next: (x: Term, prev: Term, env: RecurEnv<A, U>) => TermLike;
363
367
  };
364
368
  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 +373,7 @@ type ValuesOf<K extends Kinds> = {
369
373
  [n in keyof K]: K[n] extends Kind<infer V> ? V : never;
370
374
  };
371
375
  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> ? {
376
+ 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
377
  [k in keyof V]: Writes<V[k]>;
374
378
  } : TermLike;
375
379
  type Update<S extends Obj, M extends Obj> = Partial<{
@@ -458,6 +462,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
458
462
  prune(order: keyof R & string, after: (p: P) => TermLike): Rule<P, I, S, M, F, R, Su, Q>;
459
463
  law(name: string, f: (env: P & S & M & Su) => Cond | Forall): Law;
460
464
  private lawOf;
465
+ kindLaws(): Law[];
461
466
  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
467
  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
468
  leadsTo(name: string, spec: {
@@ -552,18 +557,18 @@ export type Decision = {
552
557
  then: C;
553
558
  goal: Goal;
554
559
  };
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>] ? {
560
+ 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
561
  [f in keyof V]: ResultOf<V[f]>;
557
562
  } : Term;
558
563
  type Result<T> = {
559
564
  [k in keyof T]: ResultOf<T[k]>;
560
565
  };
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>] ? {
566
+ 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
567
  [f in keyof V]: ValueOf<V[f]>;
563
568
  } : number;
564
- type CaseRaw<C extends Cases> = {
569
+ type CaseRaw<C extends Cases, T extends string = 'tag'> = {
565
570
  [k in keyof C]: {
566
- tag: k;
571
+ [t in T]: k;
567
572
  } & {
568
573
  [f in keyof C[k]]: C[k][f] extends Kind<infer V> ? ValueOf<V> : never;
569
574
  };
@@ -571,7 +576,7 @@ type CaseRaw<C extends Cases> = {
571
576
  type Value<T> = {
572
577
  [k in keyof T]: ValueOf<T[k]>;
573
578
  };
574
- type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any> | {
579
+ type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any, any> | Maybe<any> | {
575
580
  [f: string]: Field | number;
576
581
  };
577
582
  export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj = {}> {
package/dist/core/dsl.js CHANGED
@@ -158,6 +158,10 @@ const named = (at, n) => (typeof at === 'string' ? at + n : at(n));
158
158
  const primed = (n) => `${n}'`;
159
159
  export class Kind {
160
160
  value(name, at = '') { return this.build(name, part => v(named(at, part))); }
161
+ // What every value a step writes keeps true of its variables (a flag is 0 or 1, a choice or a case in range, a
162
+ // presence bit 0 or 1), unlike a bound such as upTo's, which a step may pass: the laws a rule's state and cells of the
163
+ // kind are proven to keep (Rule.kindLaws).
164
+ invariant(_value) { return []; }
161
165
  // A runtime value of the kind (what encode takes), for comparing a rule with the code it describes (compare).
162
166
  sample(_rand, _bound) { throw new Error('this kind draws no runtime values'); }
163
167
  // A state variable of this kind starting at a runtime value, or at a value over the params: `.state({open: bool.from(true)})`.
@@ -321,6 +325,7 @@ class BoolKind extends Kind {
321
325
  vars(name) { return [name]; }
322
326
  assume(name) { return [v(name).atMost(1)]; }
323
327
  build(name, get) { return new Bool(get(name)); }
328
+ invariant(b) { return [b.n.atMost(1)]; }
324
329
  store(name, value) { return { [name]: typeof value === 'boolean' ? (value ? 1 : 0) : count(value) }; }
325
330
  decode(name, vars) { return vars[name] === 1; }
326
331
  empty() { return false; }
@@ -370,6 +375,7 @@ class OneOfKind extends Kind {
370
375
  vars(name) { return [name]; }
371
376
  assume(name) { return [v(name).lt(this.values.length)]; }
372
377
  build(name, get) { return new Choice(get(name), this.values); }
378
+ invariant(c) { return [c.n.lt(this.values.length)]; }
373
379
  store(name, value) { return { [name]: value instanceof Choice ? value.n : this.encode(name, value)[name] }; }
374
380
  decode(name, vars) { return this.values[vars[name]]; }
375
381
  empty() { return this.values[0]; }
@@ -414,6 +420,7 @@ class MaybeKind extends Kind {
414
420
  vars(name) { return [this.has(name), ...this.of.vars(name)]; }
415
421
  assume(name) { return [v(this.has(name)).atMost(1), ...this.of.assume(name)]; }
416
422
  build(name, get) { return new Maybe(get(this.has(name)), this.of.build(name, get)); }
423
+ invariant(m) { return [m.present.atMost(1), ...this.of.invariant(m.inner)]; }
417
424
  // null for none, a Maybe as it is, any other value as some.
418
425
  store(name, value) {
419
426
  if (value === null || value === undefined)
@@ -435,6 +442,8 @@ class MaybeKind extends Kind {
435
442
  sample(rand, bound) { return rand(4) === 0 ? null : this.of.sample(rand, bound); }
436
443
  }
437
444
  export const maybe = (of) => new MaybeKind(of);
445
+ // A value present where c holds and missing elsewhere (a function's result that may be missing, say).
446
+ export const optional = (c, value) => new Maybe(count(c), value);
438
447
  const fieldName = (name, f) => `${name}.${f}`;
439
448
  const plainFields = (what, names) => { for (const f of names)
440
449
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
@@ -473,6 +482,7 @@ class RecordKind extends Kind {
473
482
  vars(name) { composite(name); return this.each((f, k) => k.vars(fieldName(name, f))).flat(); }
474
483
  assume(name) { return this.each((f, k) => k.assume(fieldName(name, f))).flat(); }
475
484
  build(name, get) { return Object.fromEntries(this.each((f, k) => [f, k.build(fieldName(name, f), get)])); }
485
+ invariant(r) { return this.each((f, k) => k.invariant(r[f])).flat(); }
476
486
  store(name, value) {
477
487
  const o = value;
478
488
  if (o === null || typeof o !== 'object')
@@ -516,19 +526,20 @@ export class Variant {
516
526
  }
517
527
  export class VariantKind extends Kind {
518
528
  cases;
529
+ tagKey;
519
530
  names;
520
- tagKey = 'tag';
521
- constructor(cases) {
531
+ constructor(cases, tagKey) {
522
532
  super();
523
533
  this.cases = cases;
534
+ this.tagKey = tagKey;
524
535
  this.names = Object.keys(cases);
525
536
  if (this.names.length === 0)
526
537
  throw new Error('a variant has a case');
527
538
  plainFields('variant', this.names);
528
539
  for (const k of this.names) {
529
540
  plainFields(`variant case ${k}`, Object.keys(cases[k]));
530
- if ('tag' in cases[k])
531
- throw new Error(`variant case ${k}: tag names the case, not a field`);
541
+ if (tagKey in cases[k])
542
+ throw new Error(`variant case ${k}: ${tagKey} names the case, not a field`);
532
543
  }
533
544
  }
534
545
  index(k) {
@@ -547,6 +558,7 @@ export class VariantKind extends Kind {
547
558
  }
548
559
  // The value of a case with these fields (terms, values or runtime values), to compare, match or pass to `when`:
549
560
  // when(held, At.make({tag: 'holding', amount, since: now}), at)
561
+ invariant(x) { return [x.tag.lt(this.names.length), ...this.names.filter(k => this.filled(k)).flatMap(k => this.payload(k).invariant(x.cases[k]))]; }
550
562
  make(write) {
551
563
  const parts = this.store('$', write);
552
564
  return this.build('$', part => { const t = parts[part]; return typeof t === 'number' ? lit(t) : t; });
@@ -586,7 +598,12 @@ export class VariantKind extends Kind {
586
598
  }
587
599
  sample(rand, bound) { const k = this.names[rand(this.names.length)]; return { [this.tagKey]: k, ...(this.filled(k) ? this.payload(k).sample(rand, bound) : {}) }; }
588
600
  }
589
- export const variant = (cases) => new VariantKind(cases);
601
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
602
+ export function variant(cases, opts = {}) {
603
+ if (opts.tag !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(opts.tag))
604
+ throw new Error(`variant: ${opts.tag} is not a plain key`);
605
+ return new VariantKind(cases, opts.tag ?? 'tag');
606
+ }
590
607
  // A string, encoded as its interned name, its length and one bit per pattern the kind declares (`like`, Cedar's glob:
591
608
  // `*` any run of characters, `\*` a star): all computed from the string itself when the input is encoded, so the bits
592
609
  // are the string's, not an answer someone supplies. Names come from one table per process, the empty string 0; a law
@@ -1084,7 +1101,7 @@ export const modBy = (c) => {
1084
1101
  [`r(x + ${c}) = r(x)`]: forall(x => f(x.plus(c)).eq(f(x))),
1085
1102
  }) });
1086
1103
  };
1087
- const reservedInRecur = ['x', 'fx', 'y', 'fy', 'prev', 'x0'];
1104
+ const reservedInRecur = ['x', 'fx', 'y', 'fy', 'prev', 'x0', 'fx0'];
1088
1105
  export const recur = (name, def, lemmas = {}, between = {}) => {
1089
1106
  const plain = /^[A-Za-z][A-Za-z0-9_]*$/;
1090
1107
  if (!plain.test(name))
@@ -1094,15 +1111,13 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1094
1111
  if (!plain.test(n) || reservedInRecur.includes(n) || n === name)
1095
1112
  throw new Error(`recur ${name}: ${n} can't name an argument or a function here (${reservedInRecur.join(', ')} and ${name} are taken)`);
1096
1113
  distinctNames(`recur ${name}`, [...args, ...uses.map(([u]) => u)]);
1097
- if (args.length > 2 && Object.keys(between).length)
1098
- throw new Error(`recur ${name}: a law between two points takes at most two further arguments`);
1099
- if (args.length > 3)
1100
- throw new Error(`recur ${name}: at most three further arguments`);
1114
+ if (args.length > 4)
1115
+ throw new Error(`recur ${name}: at most four further arguments`);
1101
1116
  const named = (n) => new Term({ op: 'var', name: n });
1102
1117
  const argsAt = (ts) => Object.fromEntries(args.map((a, i) => [a, ts[i]]));
1103
1118
  const usesAt = (at) => Object.fromEntries(uses.map(([u, d]) => [u, fnTerm(at(u), d)]));
1104
1119
  const envAt = (ts, at = u => u) => ({ ...argsAt(ts), ...usesAt(at) });
1105
- const zeroAt = (ts) => (typeof def.zero === 'function' ? def.zero(argsAt(ts)) : def.zero);
1120
+ const zeroAt = (ts, at = u => u) => (typeof def.zero === 'function' ? def.zero(envAt(ts, at)) : def.zero);
1106
1121
  // The recurrence over named variables (x, prev, the arguments; lemmas over x and fx; laws between over x, fx, y, fy),
1107
1122
  // the functions it reads by their own names: what the runtimes and the Lean export are made from.
1108
1123
  const own = args.map(named);
@@ -1117,7 +1132,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1117
1132
  if (!maps.includes(m))
1118
1133
  throw new Error(`recur ${name}: ${what} reads a function or a map it isn't given (${m})`);
1119
1134
  };
1120
- only('zero', zero, args, []);
1135
+ only('zero', zero, args, uses.map(([u]) => u));
1121
1136
  only('next', next, ['x', 'prev', ...args], uses.map(([u]) => u));
1122
1137
  const stated = Object.entries(lemmas).map(([n, f]) => { const holds = f(named('x'), named('fx'), envAt(own)).c; only(`lemma "${n}"`, holds, ['x', 'fx', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
1123
1138
  const pairs = Object.entries(between).map(([n, f]) => { const holds = f(named('x'), named('fx'), named('y'), named('fy'), envAt(own)).c; only(`law between "${n}"`, holds, ['x', 'fx', 'y', 'fy', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
@@ -1126,44 +1141,55 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1126
1141
  throw new Unproven(`${name}'s ${what}`, v); };
1127
1142
  // The lemmas: x counting up from 0 and fx its value, each lemma a law of that machine.
1128
1143
  let verdict = { proven: true, checked: true };
1144
+ const proofs = {};
1129
1145
  if (stated.length) {
1130
- const r = rule(`${name}, by induction`).params(argKinds).fns(def.fns ?? {}).inputs({}).state({ x: 0, fx: ((p) => zeroAt(args.map(a => p[a]))) })
1146
+ // A start that reads a function is a param given as that start by a fact (a start reads the params alone).
1147
+ const zeroReads = JSON.stringify(zero).includes('"op":"get"');
1148
+ let r0 = rule(`${name}, by induction`).params({ ...argKinds, ...(zeroReads ? { fx0: nat } : {}) }).fns(def.fns ?? {}).inputs({});
1149
+ if (zeroReads)
1150
+ r0 = r0.facts((p) => ({ [`fx0 is ${name}(0)`]: p.fx0.eq(zeroAt(args.map(a => p[a]))) }));
1151
+ const r = r0.state({ x: 0, fx: ((p) => (zeroReads ? p.fx0 : zeroAt(args.map(a => p[a])))) })
1131
1152
  .step((env) => ({ x: env.x.plus(1), fx: def.next(env.x, env.fx, envAt(args.map(a => env[a]))) }));
1132
- verdict = proveRule(r, Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a]))))));
1153
+ const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
1154
+ verdict = proveRule(r, laws);
1133
1155
  failed('lemmas', verdict);
1156
+ proofs.lemmas = { machine: r.machine, laws };
1134
1157
  }
1135
1158
  const js = recurJs(name, args, zero, next, uses);
1136
- const recurrence = { args, zero, next, lemmas: stated, between: pairs, uses: Object.fromEntries(uses.map(([u, d]) => [u, { arity: d.arity, ...(d.lean ? { lean: d.lean } : {}) }])) };
1159
+ const recurrence = { args, zero, next, lemmas: stated, between: pairs, uses: Object.fromEntries(uses.map(([u, d]) => [u, { arity: d.arity, ...(d.lean ? { lean: d.lean } : {}) }])), proofs };
1137
1160
  // A law about every n keys (forall counts them by the callback's own parameters).
1138
1161
  const keysOf = (n) => (f) => {
1139
1162
  if (n === 0)
1140
1163
  return f();
1141
- 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)][n - 1];
1164
+ 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),
1165
+ (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];
1142
1166
  return forall(by);
1143
1167
  };
1144
1168
  const facts = (withBetween) => (f, at = {}) => {
1145
1169
  const env = (ts) => ({ ...argsAt(ts), ...Object.fromEntries(uses.map(([u, d]) => [u, at[u] ?? fnTerm(u, d)])) });
1146
1170
  return {
1147
- [`${name}(0)`]: keysOf(args.length)((...as) => f(0, ...as).eq(zeroAt(as))),
1171
+ [`${name}(0)`]: keysOf(args.length)((...as) => f(0, ...as).eq(typeof def.zero === 'function' ? def.zero(env(as)) : def.zero)),
1148
1172
  [`${name}(x + 1)`]: keysOf(1 + args.length)((k, ...as) => f(k.plus(1), ...as).eq(def.next(k, f(k, ...as), env(as)))),
1149
1173
  ...Object.fromEntries(Object.entries(lemmas).map(([n, l]) => [n, keysOf(1 + args.length)((k, ...as) => l(k, f(k, ...as), env(as)))])),
1150
1174
  ...(withBetween ? Object.fromEntries(Object.entries(between).map(([n, l]) => [n, keysOf(2 + args.length)((k, j, ...as) => k.atMost(j).implies(l(k, f(k, ...as), j, f(j, ...as), env(as))))])) : {}),
1151
1175
  };
1152
1176
  };
1153
1177
  const base = { arity: 1 + args.length, js, lean: `Recur.${name}`, recur: recurrence, ...(uses.length ? { uses: def.fns } : {}) };
1154
- // The laws between two points: y counting up from x, fy from f(x) (a param, given as f(x) by a fact), each law a law
1155
- // of that machine beside "fy is f(y)", which lets the lemmas speak at y.
1178
+ // The laws between two points: y counting up from x and fy from fx, a value at x that meets the lemmas (as f(x)
1179
+ // does: they are given of it, as facts of the params), each law a law of that machine beside the lemmas at y. What
1180
+ // holds there for every such fx holds for f(x), and fy is f(y) by the recurrence; the machine reads no f at all.
1156
1181
  if (pairs.length) {
1157
- const self = { ...base, facts: facts(false) };
1158
- const r = rule(`${name}, between`).params({ ...argKinds, x: nat, fx: nat }).fns({ [name]: self }).inputs({})
1159
- .facts(((p) => ({ [`fx is ${name}(x)`]: p.fx.eq(p[name](p.x, ...args.map(a => p[a]))) })))
1160
- .state({ y: ((p) => p.x), fy: ((p) => p.fx) })
1161
- .step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(args.map(a => env[a]), u => `${name}.${u}`)) }));
1162
1182
  const at = (env) => args.map(a => env[a]);
1163
- const laws = [r.law(`fy is ${name}(y)`, (env) => env.fy.eq(env[name](env.y, ...at(env)))),
1164
- ...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env), u => `${name}.${u}`))))];
1183
+ let r0 = rule(`${name}, between`).params({ ...argKinds, x: nat, fx: nat }).fns(def.fns ?? {}).inputs({});
1184
+ if (stated.length)
1185
+ r0 = r0.facts((p) => Object.fromEntries(Object.entries(lemmas).map(([n, l]) => [`${n} at x`, l(p.x, p.fx, envAt(at(p)))])));
1186
+ const r = r0.state({ y: ((p) => p.x), fy: ((p) => p.fx) })
1187
+ .step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
1188
+ const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
1189
+ ...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env)))))];
1165
1190
  const v = proveRule(r, laws);
1166
1191
  failed('laws between two points', v);
1192
+ proofs.between = { machine: r.machine, laws };
1167
1193
  verdict = { proven: true, checked: verdict.checked && v.checked };
1168
1194
  }
1169
1195
  return { ...base, verdict, facts: facts(true) };
@@ -1201,7 +1227,9 @@ const recurJs = (name, args, zero, next, uses) => {
1201
1227
  }
1202
1228
  };
1203
1229
  const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); `).join('');
1204
- return `(x, ${args.map(a => `${a}, `).join('')}) => { ${inlined}let prev = ${js(zero)}; for (let x0 = 0; x0 < x; x0++) { prev = ((x, prev) => ${js(next)})(x0, prev); if (!Number.isSafeInteger(prev)) throw new RangeError(${JSON.stringify(`${name} passes the safe integers`)}); } return prev; }`;
1230
+ // The last value computed at each tuple of further arguments is kept, so reading f at x after f at a smaller x takes
1231
+ // only the steps between (a rule that reads f at a count that grows costs one step a request).
1232
+ 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; }; })()`;
1205
1233
  };
1206
1234
  // Functions with the functions they read, those first (named <function>.<use>), each with the terms its facts read them by.
1207
1235
  const flatFns = (defs) => defs.flatMap(([n, d]) => {
@@ -1223,7 +1251,7 @@ const fnSource = (name, d) => {
1223
1251
  }
1224
1252
  if (d.arity === 1)
1225
1253
  return src;
1226
- return `k => { const u = z => { let s = Math.floor(Math.sqrt(z)); while (s * s > z) s--; while ((s + 1) * (s + 1) <= z) s++; return [s - (z - s * s), z - s * s]; }; const a = []; let z = k; for (let i = 1; i < ${d.arity}; i++) { const [p, q] = u(z); a.unshift(q); z = p; } a.unshift(z); return (${src})(...a); }`;
1254
+ return `(() => { const $fn = (${src}); return k => { const u = z => { let s = Math.floor(Math.sqrt(z)); while (s * s > z) s--; while ((s + 1) * (s + 1) <= z) s++; return [s - (z - s * s), z - s * s]; }; const a = []; let z = k; for (let i = 1; i < ${d.arity}; i++) { const [p, q] = u(z); a.unshift(q); z = p; } a.unshift(z); return $fn(...a); }; })()`;
1227
1255
  };
1228
1256
  const fnTerm = (name, d) => (...args) => {
1229
1257
  if (args.length !== d.arity)
@@ -1384,6 +1412,22 @@ export class Rule {
1384
1412
  const [k1, more] = keysFor(out), holds = out.forall(v(k1), ...more.map(v)).c;
1385
1413
  return more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds };
1386
1414
  }
1415
+ // What the kinds keep true of the state and of the maps and lists of a kind (Kind.invariant: a flag 0 or 1, a choice or
1416
+ // a case in range, a presence bit 0 or 1), as laws: proveRule, proveSteps and proveLeadsTo prove them beside the laws
1417
+ // they are given, so a law may lean on "the hold is idle, holding or released" without stating it; give them to
1418
+ // toLean (or the kernel) with the rest.
1419
+ kindLaws() {
1420
+ const out = [];
1421
+ const of = (env, n) => env[n];
1422
+ for (const [n, k] of this.spec.kinded ?? [])
1423
+ if (k.invariant(k.value(n)).length)
1424
+ out.push(this.law(`${n} is a value of its kind`, env => all(...k.invariant(of(env, n)))));
1425
+ for (const [n, k] of [...this.spec.cells ?? [], ...this.spec.items ?? []])
1426
+ if (k.invariant(k.value(n)).length) {
1427
+ out.push(this.law(`every ${n} cell is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
1428
+ }
1429
+ return out;
1430
+ }
1387
1431
  // A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
1388
1432
  // the request, state, maps, flags, sums, queries) to the state, maps and sums after it. `when` narrows the requests.
1389
1433
  // counter.stepLaw('never goes back', ({used}, after) => used.atMost(after.used))
@@ -1876,7 +1920,7 @@ const encode = (kinds, values) => Object.assign({}, ...kinds.map(([n, k]) => k.e
1876
1920
  export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [] });
1877
1921
  // The kind a result field is kept in.
1878
1922
  const kindOf = (t) => (t instanceof Cond ? bool : t instanceof Int ? int : t instanceof Decimal ? decimal(t.places) : t instanceof Choice ? oneOf(...t.values) : t instanceof Str ? str({ like: t.patterns })
1879
- : t instanceof Variant ? t.kind : isRecord(t) ? record(Object.fromEntries(Object.entries(t).map(([f, x]) => [f, kindOf(x)]))) : nat);
1923
+ : t instanceof Variant ? t.kind : t instanceof Maybe ? maybe(kindOf(t.inner)) : isRecord(t) ? record(Object.fromEntries(Object.entries(t).map(([f, x]) => [f, kindOf(x)]))) : nat);
1880
1924
  export class Fun {
1881
1925
  r;
1882
1926
  out;
@@ -2019,16 +2063,18 @@ const goalsVerdict = (goals) => {
2019
2063
  }
2020
2064
  return { proven: true, checked: goals.every(g => check(g, trees[g.name])), trees };
2021
2065
  };
2066
+ // The kinds' laws first, those the caller didn't pass already (by name).
2067
+ const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.name === k.name)), ...laws];
2022
2068
  export function proveRule(r, laws) {
2023
- const out = proveLaws(r.machine, laws);
2069
+ const out = proveLaws(r.machine, withKinds(r, laws));
2024
2070
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2025
2071
  }
2026
2072
  export function proveSteps(r, laws, stepLaws) {
2027
- const out = proveStepLaws(r.machine, laws, stepLaws);
2073
+ const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
2028
2074
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2029
2075
  }
2030
2076
  export function proveLeadsTo(r, laws, l) {
2031
- const out = proveLiveness(r.machine, laws, l);
2077
+ const out = proveLiveness(r.machine, withKinds(r, laws), l);
2032
2078
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2033
2079
  }
2034
2080
  export const proveDecision = (d) => goalsVerdict([d.goal]);
@@ -6,8 +6,8 @@ export type Mint<K extends string, A> = <S extends AnyNamed[]>(args: A, ...about
6
6
  export { Unproven };
7
7
  type AnyFun = Fun<any, any, any>;
8
8
  type Conditions<O> = {
9
- [k in keyof O]: O[k] extends boolean ? k : never;
10
- }[keyof O] & string;
9
+ [k in keyof O & string]: O[k] extends boolean ? k : O[k] extends Record<string, unknown> ? `${k}.${Conditions<O[k]>}` : never;
10
+ }[keyof O & string];
11
11
  type ArgsOf<F extends AnyFun> = Parameters<F['run']>[0];
12
12
  type ParamsOf<F extends AnyFun> = NonNullable<Parameters<F['run']>[1]>;
13
13
  export declare function guard<K extends string, F extends AnyFun>(prover: Prover<K>, fn: F, field: Conditions<ReturnType<F['run']>>, opts: {
package/dist/core/gdp.js CHANGED
@@ -4,6 +4,7 @@ import { allowed, requestEnv } from "./watch.js";
4
4
  export { Unproven };
5
5
  const proven = (what, v) => { if (!(v.proven && v.checked))
6
6
  throw new Unproven(what, v); };
7
+ const atPath = (o, path) => path.split('.').reduce((x, f) => (typeof x === 'object' && x !== null ? x[f] : undefined), o);
7
8
  // A proof wherever `fn`'s result `field` holds. Every law in `laws` must have been stated about `fn` (fn.law or
8
9
  // fn.relate) and is proven now; the arguments it is later called with must meet the function's kinds and `assume`.
9
10
  // const access = fun('document access').inputs({...}).returns(env => ({permit: permit(env)}))
@@ -23,7 +24,7 @@ export function guard(prover, fn, field, opts) {
23
24
  const input = r.encodeInput(args);
24
25
  if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
25
26
  return null;
26
- return fn.run(args, params)[field] === true ? prover.prove(...about) : null;
27
+ return atPath(fn.run(args, params), field) === true ? prover.prove(...about) : null;
27
28
  };
28
29
  }
29
30
  // A proof for each request whose step takes `flag`, the rule's laws (and step laws) proven now. One gate holds one
@@ -88,6 +88,16 @@ export type Recurrence = {
88
88
  arity: number;
89
89
  lean?: string;
90
90
  }>;
91
+ proofs?: {
92
+ lemmas?: {
93
+ machine: Machine;
94
+ laws: Law[];
95
+ };
96
+ between?: {
97
+ machine: Machine;
98
+ laws: Law[];
99
+ };
100
+ };
91
101
  };
92
102
  export type History = {
93
103
  keep: C;
@@ -92,6 +92,7 @@ export declare function smachineToLean(id: string, given: Machine, laws: Law[],
92
92
  loop: Loop;
93
93
  };
94
94
  };
95
+ noFacts?: boolean;
95
96
  }): string;
96
97
  type Uses = Record<string, {
97
98
  lean?: string;