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/dist/core/dsl.js CHANGED
@@ -15,9 +15,9 @@
15
15
  //
16
16
  // A step returns its updates as one object: every right-hand side reads the state before the step, as the kernel and
17
17
  // both runtimes do.
18
- import { carriedView, compileFunction, evalC, interpret, loadJs, numbersOf } from "./machine.js";
18
+ import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf } from "./machine.js";
19
19
  import { allowed, keysOf, requestEnv, watch } from "./watch.js";
20
- import { check, loopObligations } from "../kernel/kernel.js";
20
+ import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
21
21
  import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
22
22
  // --- terms and conditions -------------------------------------------------------------------------------------------
23
23
  const naturalConst = (v) => {
@@ -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
@@ -850,6 +867,89 @@ export class CellsRef {
850
867
  return { parts: Object.fromEntries(this.kind.vars(this.name).map(part => [part, this.map(part).each(k => partsOf(this.kind, this.name, f(k))[part])])) };
851
868
  }
852
869
  }
870
+ export class CarriedList {
871
+ name;
872
+ kind;
873
+ len;
874
+ folds;
875
+ map;
876
+ lemmas;
877
+ fold;
878
+ constructor(name, kind, len, folds, map, lemmas = {}) {
879
+ this.name = name;
880
+ this.kind = kind;
881
+ this.len = len;
882
+ this.folds = folds;
883
+ this.map = map;
884
+ this.lemmas = lemmas;
885
+ this.fold = Object.fromEntries(Object.keys(folds).map(f => [f, map(`${name}$${f}`).at(len)]));
886
+ }
887
+ at(i) { return this.kind.build(this.name, part => this.map(part).at(i)); }
888
+ // The running value of a fold after the first i items.
889
+ after(fold, i) { return this.map(`${this.name}$${fold}`).at(i); }
890
+ every(f) { return everyKey(k => k.lt(this.len).implies(f(this.at(k), k))); }
891
+ some(f) { return this.every((x, i) => f(x, i).not()).not(); }
892
+ has(x) { return this.some(y => same(y, x)); }
893
+ // Each item's `by` below the next one's (so no two alike).
894
+ increasing(by) { return everyKey(k => k.plus(1).lt(this.len).implies(by(this.at(k)).lt(by(this.at(k.plus(1)))))); }
895
+ // What every request is assumed to meet: each fold's start, and at every key one condition (the item's own laws and
896
+ // each fold's step below the length, each lemma up to it), so the prover closes one quantifier, not one per part.
897
+ equations() {
898
+ const folds = Object.entries(this.folds), lemmas = Object.values(this.lemmas);
899
+ const at = (k) => {
900
+ const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x)));
901
+ const below = [...this.kind.invariant(x), ...steps];
902
+ return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k))))] : []));
903
+ };
904
+ const any = this.kind.invariant(this.kind.value(this.name)).length + folds.length + lemmas.length > 0;
905
+ return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(d.from)), ...(any ? [everyKey(at)] : [])];
906
+ }
907
+ running(k) { return Object.fromEntries(Object.keys(this.folds).map(f => [f, this.after(f, k)])); }
908
+ // The lemmas, proven: the folds as a machine counting i up from 0 over items that are functions of i known only by
909
+ // the kind's own laws, each lemma a law of it (so it holds after every prefix of every list).
910
+ prove() {
911
+ if (!Object.keys(this.lemmas).length)
912
+ return { proven: true, checked: true };
913
+ const parts = this.kind.vars(this.name), fname = (p) => `item$${p.replaceAll('.', '$')}`;
914
+ const item = (i) => this.kind.build(this.name, p => new Term({ op: 'get', map: fname(p), key: i.e }));
915
+ // Every item is a value of the kind (its own laws, as a fact of the first part's function).
916
+ const kindFact = this.kind.invariant(this.kind.value(this.name)).length ? { 'every item is a value of its kind': forall(k => all(...this.kind.invariant(item(k)))) } : {};
917
+ const fns = Object.fromEntries(parts.map((p, j) => [fname(p), fn(1, 'x => 0', j === 0 ? { facts: () => kindFact } : {})]));
918
+ const folds = Object.entries(this.folds);
919
+ const r = rule(`${this.name}'s folds, by induction`).fns(fns).inputs({}).state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.from])) })
920
+ .step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i))])) }));
921
+ const accs = (env) => Object.fromEntries(folds.map(([f]) => [f, env[`acc$${f}`]]));
922
+ return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i))));
923
+ }
924
+ // Every carried map it is kept in: the items' parts, then each fold's.
925
+ get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
926
+ // A request's list as those maps and its length: each fold computed by its step, as the builders compute it.
927
+ encode(raw) {
928
+ if (!Array.isArray(raw))
929
+ throw new RangeError(`${this.name} must be a list, not ${shown(raw)}`);
930
+ const items = raw.map(x => this.kind.encode(this.name, x));
931
+ const maps = Object.fromEntries(this.maps.map(m => [m, new Map()]));
932
+ items.forEach((enc, i) => { for (const [part, x] of Object.entries(enc))
933
+ if (x !== 0)
934
+ maps[part].set(i, x); });
935
+ for (const [f, d] of Object.entries(this.folds)) {
936
+ const acc = maps[`${this.name}$${f}`];
937
+ let value = d.from;
938
+ if (value !== 0)
939
+ acc.set(0, value);
940
+ items.forEach((enc, i) => {
941
+ const item = this.kind.build(this.name, part => new Term({ op: 'const', value: enc[part] }, enc[part]));
942
+ const next = valOf(d.step(new Term({ op: 'const', value }, value), item));
943
+ if (next === undefined || !Number.isSafeInteger(next) || next < 0)
944
+ throw new RangeError(`${this.name}'s fold ${f} reads more than its items`);
945
+ value = next;
946
+ if (value !== 0)
947
+ acc.set(i + 1, value);
948
+ });
949
+ }
950
+ return { maps, len: items.length };
951
+ }
952
+ }
853
953
  export class ItemsRef {
854
954
  name;
855
955
  kind;
@@ -1084,7 +1184,7 @@ export const modBy = (c) => {
1084
1184
  [`r(x + ${c}) = r(x)`]: forall(x => f(x.plus(c)).eq(f(x))),
1085
1185
  }) });
1086
1186
  };
1087
- const reservedInRecur = ['x', 'fx', 'y', 'fy', 'prev', 'x0'];
1187
+ const reservedInRecur = ['x', 'fx', 'y', 'fy', 'prev', 'x0', 'fx0'];
1088
1188
  export const recur = (name, def, lemmas = {}, between = {}) => {
1089
1189
  const plain = /^[A-Za-z][A-Za-z0-9_]*$/;
1090
1190
  if (!plain.test(name))
@@ -1094,15 +1194,13 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1094
1194
  if (!plain.test(n) || reservedInRecur.includes(n) || n === name)
1095
1195
  throw new Error(`recur ${name}: ${n} can't name an argument or a function here (${reservedInRecur.join(', ')} and ${name} are taken)`);
1096
1196
  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`);
1197
+ if (args.length > 4)
1198
+ throw new Error(`recur ${name}: at most four further arguments`);
1101
1199
  const named = (n) => new Term({ op: 'var', name: n });
1102
1200
  const argsAt = (ts) => Object.fromEntries(args.map((a, i) => [a, ts[i]]));
1103
1201
  const usesAt = (at) => Object.fromEntries(uses.map(([u, d]) => [u, fnTerm(at(u), d)]));
1104
1202
  const envAt = (ts, at = u => u) => ({ ...argsAt(ts), ...usesAt(at) });
1105
- const zeroAt = (ts) => (typeof def.zero === 'function' ? def.zero(argsAt(ts)) : def.zero);
1203
+ const zeroAt = (ts, at = u => u) => (typeof def.zero === 'function' ? def.zero(envAt(ts, at)) : def.zero);
1106
1204
  // The recurrence over named variables (x, prev, the arguments; lemmas over x and fx; laws between over x, fx, y, fy),
1107
1205
  // the functions it reads by their own names: what the runtimes and the Lean export are made from.
1108
1206
  const own = args.map(named);
@@ -1117,7 +1215,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1117
1215
  if (!maps.includes(m))
1118
1216
  throw new Error(`recur ${name}: ${what} reads a function or a map it isn't given (${m})`);
1119
1217
  };
1120
- only('zero', zero, args, []);
1218
+ only('zero', zero, args, uses.map(([u]) => u));
1121
1219
  only('next', next, ['x', 'prev', ...args], uses.map(([u]) => u));
1122
1220
  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
1221
  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 +1224,55 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1126
1224
  throw new Unproven(`${name}'s ${what}`, v); };
1127
1225
  // The lemmas: x counting up from 0 and fx its value, each lemma a law of that machine.
1128
1226
  let verdict = { proven: true, checked: true };
1227
+ const proofs = {};
1129
1228
  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]))) })
1229
+ // A start that reads a function is a param given as that start by a fact (a start reads the params alone).
1230
+ const zeroReads = JSON.stringify(zero).includes('"op":"get"');
1231
+ let r0 = rule(`${name}, by induction`).params({ ...argKinds, ...(zeroReads ? { fx0: nat } : {}) }).fns(def.fns ?? {}).inputs({});
1232
+ if (zeroReads)
1233
+ r0 = r0.facts((p) => ({ [`fx0 is ${name}(0)`]: p.fx0.eq(zeroAt(args.map(a => p[a]))) }));
1234
+ const r = r0.state({ x: 0, fx: ((p) => (zeroReads ? p.fx0 : zeroAt(args.map(a => p[a])))) })
1131
1235
  .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]))))));
1236
+ const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
1237
+ verdict = proveRule(r, laws);
1133
1238
  failed('lemmas', verdict);
1239
+ proofs.lemmas = { machine: r.machine, laws };
1134
1240
  }
1135
1241
  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 } : {}) }])) };
1242
+ 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
1243
  // A law about every n keys (forall counts them by the callback's own parameters).
1138
1244
  const keysOf = (n) => (f) => {
1139
1245
  if (n === 0)
1140
1246
  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];
1247
+ const by = [(a) => f(a), (a, b) => f(a, b), (a, b, c) => f(a, b, c), (a, b, c, d) => f(a, b, c, d),
1248
+ (a, b, c, d, e) => f(a, b, c, d, e), (a, b, c, d, e, g) => f(a, b, c, d, e, g)][n - 1];
1142
1249
  return forall(by);
1143
1250
  };
1144
1251
  const facts = (withBetween) => (f, at = {}) => {
1145
1252
  const env = (ts) => ({ ...argsAt(ts), ...Object.fromEntries(uses.map(([u, d]) => [u, at[u] ?? fnTerm(u, d)])) });
1146
1253
  return {
1147
- [`${name}(0)`]: keysOf(args.length)((...as) => f(0, ...as).eq(zeroAt(as))),
1254
+ [`${name}(0)`]: keysOf(args.length)((...as) => f(0, ...as).eq(typeof def.zero === 'function' ? def.zero(env(as)) : def.zero)),
1148
1255
  [`${name}(x + 1)`]: keysOf(1 + args.length)((k, ...as) => f(k.plus(1), ...as).eq(def.next(k, f(k, ...as), env(as)))),
1149
1256
  ...Object.fromEntries(Object.entries(lemmas).map(([n, l]) => [n, keysOf(1 + args.length)((k, ...as) => l(k, f(k, ...as), env(as)))])),
1150
1257
  ...(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
1258
  };
1152
1259
  };
1153
1260
  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.
1261
+ // 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)
1262
+ // does: they are given of it, as facts of the params), each law a law of that machine beside the lemmas at y. What
1263
+ // 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
1264
  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
1265
  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}`))))];
1266
+ let r0 = rule(`${name}, between`).params({ ...argKinds, x: nat, fx: nat }).fns(def.fns ?? {}).inputs({});
1267
+ if (stated.length)
1268
+ r0 = r0.facts((p) => Object.fromEntries(Object.entries(lemmas).map(([n, l]) => [`${n} at x`, l(p.x, p.fx, envAt(at(p)))])));
1269
+ const r = r0.state({ y: ((p) => p.x), fy: ((p) => p.fx) })
1270
+ .step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
1271
+ const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
1272
+ ...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env)))))];
1165
1273
  const v = proveRule(r, laws);
1166
1274
  failed('laws between two points', v);
1275
+ proofs.between = { machine: r.machine, laws };
1167
1276
  verdict = { proven: true, checked: verdict.checked && v.checked };
1168
1277
  }
1169
1278
  return { ...base, verdict, facts: facts(true) };
@@ -1201,7 +1310,9 @@ const recurJs = (name, args, zero, next, uses) => {
1201
1310
  }
1202
1311
  };
1203
1312
  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; }`;
1313
+ // The last value computed at each tuple of further arguments is kept, so reading f at x after f at a smaller x takes
1314
+ // only the steps between (a rule that reads f at a count that grows costs one step a request).
1315
+ 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
1316
  };
1206
1317
  // Functions with the functions they read, those first (named <function>.<use>), each with the terms its facts read them by.
1207
1318
  const flatFns = (defs) => defs.flatMap(([n, d]) => {
@@ -1223,7 +1334,7 @@ const fnSource = (name, d) => {
1223
1334
  }
1224
1335
  if (d.arity === 1)
1225
1336
  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); }`;
1337
+ 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
1338
  };
1228
1339
  const fnTerm = (name, d) => (...args) => {
1229
1340
  if (args.length !== d.arity)
@@ -1343,6 +1454,27 @@ export class Rule {
1343
1454
  facts(f) { return this.with({ facts: [...(this.spec.facts ?? []), f] }); }
1344
1455
  // Maps each request carries whole (a set of names, say: cells 0 or 1), every cell past them 0; read like any map.
1345
1456
  carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
1457
+ // A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
1458
+ // .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
1459
+ carriesList(name, of, folds = {}, lemmas = {}) {
1460
+ const entries = [[name, of, folds, lemmas]];
1461
+ for (const [n, , fs] of entries)
1462
+ for (const f of Object.keys(fs))
1463
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
1464
+ throw new Error(`${n}: ${f} is not a plain fold name`);
1465
+ // The folds' lemmas, proven now (a lemma that isn't throws, with the verdict).
1466
+ for (const [n, k, fs, ls] of entries) {
1467
+ const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls).prove();
1468
+ if (!(v2.proven && v2.checked))
1469
+ throw new Unproven(`${n}'s fold lemmas`, v2);
1470
+ }
1471
+ for (const [n, , fs] of entries)
1472
+ for (const f of Object.keys(fs))
1473
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
1474
+ throw new Error(`${n}: ${f} is not a plain fold name`);
1475
+ const maps = entries.flatMap(([n, k, fs]) => [...k.vars(n), ...Object.keys(fs).map(f => `${n}$${f}`)]);
1476
+ return this.with({ carried: [...(this.spec.carried ?? []), ...maps], inputs: [...this.spec.inputs, ...entries.map(([n]) => [`${n}_len`, nat])], lists2: [...(this.spec.lists2 ?? []), ...entries] });
1477
+ }
1346
1478
  // Named conditions of the step, decided once per step before it; each call's flags may read the earlier calls'. A flag
1347
1479
  // named again replaces the earlier definition in place (what a variant needs: one call, the step unchanged); the new
1348
1480
  // definition may read only the flags defined before the original's place, as the kernel checks.
@@ -1384,6 +1516,22 @@ export class Rule {
1384
1516
  const [k1, more] = keysFor(out), holds = out.forall(v(k1), ...more.map(v)).c;
1385
1517
  return more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds };
1386
1518
  }
1519
+ // 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
1520
+ // a case in range, a presence bit 0 or 1), as laws: proveRule, proveSteps and proveLeadsTo prove them beside the laws
1521
+ // they are given, so a law may lean on "the hold is idle, holding or released" without stating it; give them to
1522
+ // toLean (or the kernel) with the rest.
1523
+ kindLaws() {
1524
+ const out = [];
1525
+ const of = (env, n) => env[n];
1526
+ for (const [n, k] of this.spec.kinded ?? [])
1527
+ if (k.invariant(k.value(n)).length)
1528
+ out.push(this.law(`${n} is a value of its kind`, env => all(...k.invariant(of(env, n)))));
1529
+ for (const [n, k] of [...this.spec.cells ?? [], ...this.spec.items ?? []])
1530
+ if (k.invariant(k.value(n)).length) {
1531
+ out.push(this.law(`every ${n} cell is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
1532
+ }
1533
+ return out;
1534
+ }
1387
1535
  // A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
1388
1536
  // the request, state, maps, flags, sums, queries) to the state, maps and sums after it. `when` narrows the requests.
1389
1537
  // counter.stepLaw('never goes back', ({used}, after) => used.atMost(after.used))
@@ -1456,6 +1604,8 @@ export class Rule {
1456
1604
  // r.relate('the secret is irrelevant', (a, b) => a.user.eq(b.user), (a, b) => a.allowed.iff(b.allowed))
1457
1605
  relate(name, given, then) {
1458
1606
  const s = this.spec, b = (n) => `b.${n}`, own = [...s.inputs.map(([n]) => n), ...(s.carried ?? [])];
1607
+ if (s.lists2?.length)
1608
+ throw new Error(`${s.name}: a decision about two requests takes no carried list`);
1459
1609
  const second = (env) => ({ ...env, ...Object.fromEntries(own.map(n => [n, env[b(n)]])) });
1460
1610
  const pair = new Rule({ ...s, inputs: [...s.inputs, ...s.inputs.map(([n, k]) => [b(n), k])], carried: [...(s.carried ?? []), ...(s.carried ?? []).map(b)],
1461
1611
  assume: [...s.assume, ...s.assume.map(f => (env) => f(second(env)))], flags: [], step: undefined, record: undefined, sums: [], queries: [], keep: undefined, relations: undefined, prune: undefined });
@@ -1499,6 +1649,12 @@ export class Rule {
1499
1649
  out[n] = k.value(n);
1500
1650
  for (const n of this.spec.carried ?? [])
1501
1651
  out[n] = new MapRef(n, 0);
1652
+ for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
1653
+ const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls);
1654
+ for (const part of l.maps)
1655
+ delete out[part];
1656
+ out[n] = l;
1657
+ }
1502
1658
  }
1503
1659
  if (parts.state !== false) {
1504
1660
  for (const [n] of this.spec.state)
@@ -1609,7 +1765,7 @@ export class Rule {
1609
1765
  state[n] = hoist.term(termOf(u));
1610
1766
  }
1611
1767
  const pre = [...this.kindAssumptions('params'), ...s.pre.map(f => f(pEnv).c)];
1612
- const assume = [...this.kindAssumptions('inputs'), ...s.assume.map(f => hoist.cond(f(env).c))];
1768
+ const assume = [...this.kindAssumptions('inputs'), ...(s.lists2 ?? []).flatMap(([n]) => env[n].equations().map(c => hoist.cond(c.c))), ...s.assume.map(f => hoist.cond(f(env).c))];
1613
1769
  placed();
1614
1770
  // Functions, and their facts: each function's own (named after it), then the rule's.
1615
1771
  // The functions a function reads come first, each named <function>.<use>.
@@ -1821,9 +1977,16 @@ export class Rule {
1821
1977
  // Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
1822
1978
  encodeParams(values) { return encode(this.spec.params, values); }
1823
1979
  // A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural.
1980
+ // A carried list is an array of its items' runtime values (its length and folds computed from them).
1981
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1824
1982
  encodeInput(values) {
1825
- const out = encode(this.spec.inputs, values);
1983
+ const lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0)).encode(values[n])]));
1984
+ const out = encode(this.spec.inputs, { ...values, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
1985
+ for (const l of Object.values(lists))
1986
+ Object.assign(out, l.maps);
1826
1987
  for (const n of this.spec.carried ?? []) {
1988
+ if (n in out)
1989
+ continue;
1827
1990
  const given = values[n];
1828
1991
  out[n] = given instanceof Map ? new Map(given) : new Map([...given].map(key => [key, 1]));
1829
1992
  }
@@ -1835,6 +1998,11 @@ export class Rule {
1835
1998
  const out = Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound)));
1836
1999
  for (const n of this.spec.carried ?? [])
1837
2000
  out[n] = new Map(Array.from({ length: bound + 1 }, (_, key) => [key, rand(2)]).filter(([, cell]) => cell === 1));
2001
+ // A carried list: items drawn by their kind, its length and folds as the encoding computes them.
2002
+ for (const [n, k, fs] of this.spec.lists2 ?? []) {
2003
+ const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
2004
+ Object.assign(out, l.maps, { [`${n}_len`]: l.len });
2005
+ }
1838
2006
  return out;
1839
2007
  }
1840
2008
  // The state's scalars as runtime values: each kinded variable as its kind decodes it (an integer as one signed
@@ -1876,7 +2044,7 @@ const encode = (kinds, values) => Object.assign({}, ...kinds.map(([n, k]) => k.e
1876
2044
  export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [] });
1877
2045
  // The kind a result field is kept in.
1878
2046
  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);
2047
+ : 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
2048
  export class Fun {
1881
2049
  r;
1882
2050
  out;
@@ -2019,16 +2187,18 @@ const goalsVerdict = (goals) => {
2019
2187
  }
2020
2188
  return { proven: true, checked: goals.every(g => check(g, trees[g.name])), trees };
2021
2189
  };
2190
+ // The kinds' laws first, those the caller didn't pass already (by name).
2191
+ const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.name === k.name)), ...laws];
2022
2192
  export function proveRule(r, laws) {
2023
- const out = proveLaws(r.machine, laws);
2193
+ const out = proveLaws(r.machine, withKinds(r, laws));
2024
2194
  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
2195
  }
2026
2196
  export function proveSteps(r, laws, stepLaws) {
2027
- const out = proveStepLaws(r.machine, laws, stepLaws);
2197
+ const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
2028
2198
  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
2199
  }
2030
2200
  export function proveLeadsTo(r, laws, l) {
2031
- const out = proveLiveness(r.machine, laws, l);
2201
+ const out = proveLiveness(r.machine, withKinds(r, laws), l);
2032
2202
  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
2203
  }
2034
2204
  export const proveDecision = (d) => goalsVerdict([d.goal]);
@@ -2059,3 +2229,112 @@ export function crossCheckDecision(d, opts = {}) {
2059
2229
  return { checked };
2060
2230
  }
2061
2231
  export const proveLoop = (l, laws) => goalsVerdict(loopObligations(l.loop, laws, l.rankTerms));
2232
+ export class FoldRule {
2233
+ spec;
2234
+ constructor(spec) {
2235
+ this.spec = spec;
2236
+ }
2237
+ with(more) { return new FoldRule({ ...this.spec, ...more }); }
2238
+ get name() { return this.spec.name; }
2239
+ params(kinds) { return this.with({ params: [...this.spec.params, ...Object.entries(kinds)] }); }
2240
+ inputs(kinds) { return this.with({ inputs: [...this.spec.inputs, ...Object.entries(kinds)] }); }
2241
+ pre(f) { return this.with({ pre: [...this.spec.pre, f] }); }
2242
+ // What every leaf's and node's inputs satisfy (with their kinds' own assumptions).
2243
+ assume(f) { return this.with({ assume: [...this.spec.assume, f] }); }
2244
+ leaf(f) { return this.with({ leaf: f }); }
2245
+ node(f) { return this.with({ node: f }); }
2246
+ env(inputs = true) {
2247
+ const out = {};
2248
+ for (const [n, k] of this.spec.params)
2249
+ out[n] = k.value(n);
2250
+ if (inputs)
2251
+ for (const [n, k] of this.spec.inputs)
2252
+ out[n] = k.value(n);
2253
+ return out;
2254
+ }
2255
+ names() {
2256
+ if (!this.spec.leaf)
2257
+ throw new Error(`${this.spec.name}: a fold needs a leaf`);
2258
+ return Object.keys(this.spec.leaf(this.env()));
2259
+ }
2260
+ side(prefix) { return Object.fromEntries(this.names().map(n => [n, v(`${prefix}${n}`)])); }
2261
+ // A law about the values (and the params): it reads no input.
2262
+ law(name, f) { return { name, holds: f({ ...this.env(false), ...this.side('') }).c }; }
2263
+ get fold() {
2264
+ const s = this.spec;
2265
+ if (!s.leaf || !s.node)
2266
+ throw new Error(`${s.name}: a fold needs a leaf and a node`);
2267
+ const env = this.env(), pEnv = this.env(false);
2268
+ const pre = [...s.params.flatMap(([n, k]) => k.assume(n)).map(c => c.c), ...s.pre.map(f => f(pEnv).c)];
2269
+ const assume = [...s.inputs.flatMap(([n, k]) => k.assume(n)).map(c => c.c), ...s.assume.map(f => f(env).c)];
2270
+ const terms = (r) => Object.fromEntries(Object.entries(r).map(([n, t]) => [n, termOf(t)]));
2271
+ const leaf = terms(s.leaf(env)), node = terms(s.node(env, this.side('l_'), this.side('r_')));
2272
+ return { name: s.name, params: s.params.flatMap(([n, k]) => k.vars(n)), inputs: s.inputs.flatMap(([n, k]) => k.vars(n)), leaf, node,
2273
+ ...(pre.length ? { pre: pre.length === 1 ? pre[0] : { op: 'and', xs: pre } } : {}), ...(assume.length ? { assume: assume.length === 1 ? assume[0] : { op: 'and', xs: assume } } : {}) };
2274
+ }
2275
+ // The values of a tree, by the terms that were proven (each leaf's and node's inputs as their kinds encode them; one
2276
+ // that doesn't meet `assume` is refused, as the laws are claimed only of trees that meet it).
2277
+ run(tree, params = {}) {
2278
+ const f = this.fold, ps = encode(this.spec.params, params), none = { name: f.name, params: [], inputs: [], state: {}, step: {} };
2279
+ const st = { scalars: {}, maps: {} };
2280
+ if (f.pre && !evalC(f.pre, ps, st, none))
2281
+ throw new RangeError(`${f.name}: the params don't meet its precondition`);
2282
+ const go = (t) => {
2283
+ const own = 'leaf' in t ? t.leaf : t.node, env = { ...ps, ...encode(this.spec.inputs, own) };
2284
+ if (f.assume && !evalC(f.assume, env, st, none))
2285
+ throw new RangeError(`${f.name}: ${JSON.stringify(own)} doesn't meet its assumption`);
2286
+ if ('leaf' in t)
2287
+ return Object.fromEntries(Object.entries(f.leaf).map(([n, e]) => [n, evalE(e, env, st, none)]));
2288
+ const l = go(t.left), r = go(t.right);
2289
+ const both = { ...env, ...Object.fromEntries(Object.entries(l).map(([n, x]) => [`l_${n}`, x])), ...Object.fromEntries(Object.entries(r).map(([n, x]) => [`r_${n}`, x])) };
2290
+ return Object.fromEntries(Object.entries(f.node).map(([n, e]) => [n, evalE(e, both, st, none)]));
2291
+ };
2292
+ return go(tree);
2293
+ }
2294
+ // The builders against what they built, on random trees: each value by the callbacks' own arithmetic against the
2295
+ // terms the kernel proved (as run evaluates them), and each law's callback against its condition.
2296
+ crossCheck(laws = [], opts = {}) {
2297
+ const s = this.spec, rand = opts.rand ?? seeded(13), bound = opts.bound ?? 6, f = this.fold;
2298
+ let checked = 0;
2299
+ const sample = (kinds) => Object.fromEntries(kinds.map(([n, k]) => [n, k.sample(rand, bound)]));
2300
+ const tree = (d) => (d === 0 || rand(3) === 0 ? { leaf: sample(s.inputs) } : { node: sample(s.inputs), left: tree(d - 1), right: tree(d - 1) });
2301
+ const known = (vals) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [n, new Term({ op: 'const', value: x }, x)]));
2302
+ for (let t = 0; t < (opts.trials ?? 200); t++) {
2303
+ const params = sample(s.params), ps = encode(s.params, params);
2304
+ const pv = known(ps), pEnv = Object.fromEntries(s.params.map(([n, k]) => [n, k.build(n, part => pv[part])]));
2305
+ const builder = (tr) => {
2306
+ const own = 'leaf' in tr ? tr.leaf : tr.node, iv = known(encode(s.inputs, own)), env = { ...pEnv, ...Object.fromEntries(s.inputs.map(([n, k]) => [n, k.build(n, part => iv[part])])) };
2307
+ const out = 'leaf' in tr ? s.leaf(env) : (() => { const l = builder(tr.left), r = builder(tr.right); return l && r ? s.node(env, known(l), known(r)) : undefined; })();
2308
+ if (!out)
2309
+ return undefined;
2310
+ const vals = Object.entries(out).map(([n, x]) => [n, valOf(x)]);
2311
+ return vals.every(([, x]) => x !== undefined) ? Object.fromEntries(vals) : undefined;
2312
+ };
2313
+ const tr = tree(opts.depth ?? 3);
2314
+ let ours;
2315
+ try {
2316
+ ours = this.run(tr, params);
2317
+ }
2318
+ catch {
2319
+ continue;
2320
+ }
2321
+ const theirs = builder(tr);
2322
+ if (!theirs)
2323
+ continue;
2324
+ for (const [n, x] of Object.entries(theirs)) {
2325
+ checked++;
2326
+ if (ours[n] !== x)
2327
+ return { checked, failure: `${f.name}: ${n} is ${x} by its callbacks, ${ours[n]} by the terms proven, for ${JSON.stringify(tr)}` };
2328
+ }
2329
+ for (const law of laws) {
2330
+ const ok = evalC(law.holds, { ...ps, ...ours }, { scalars: {}, maps: {} }, { name: f.name, params: [], inputs: [], state: {}, step: {} });
2331
+ checked++;
2332
+ if (!ok)
2333
+ return { checked, failure: `${f.name}: law ${law.name} fails for ${JSON.stringify(tr)} with params ${JSON.stringify(params)}` };
2334
+ }
2335
+ }
2336
+ return { checked };
2337
+ }
2338
+ }
2339
+ export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
2340
+ export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, laws));
@@ -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;