gdp-wired 0.1.0 → 0.3.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.
@@ -1,5 +1,5 @@
1
1
  import { add, k, scale, solve, sub } from "../core/solver.js";
2
- import { canon, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, obligations, stepLawObligations, truth, unreachable, withProducts } from "./kernel.js";
2
+ import { canon, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
3
3
  // The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
4
4
  function openE(e, path, out) {
5
5
  switch (e.op) {
@@ -32,7 +32,7 @@ function openE(e, path, out) {
32
32
  function openC(c, path, out) {
33
33
  if (truth(c, path) !== undefined)
34
34
  return;
35
- if (c.op === 'flag')
35
+ if (c.op === 'flag' || c.op === 'all')
36
36
  return;
37
37
  if (c.op === 'not')
38
38
  return openC(c.x, path, out);
@@ -68,7 +68,7 @@ function openH(c, path, out, positive = true) {
68
68
  openC(c, path, out);
69
69
  return;
70
70
  }
71
- if (c.op === 'flag')
71
+ if (c.op === 'flag' || c.op === 'all')
72
72
  return;
73
73
  const before = out.length;
74
74
  openE(c.a, path, out), openE(c.b, path, out);
@@ -113,8 +113,9 @@ function parse(text) {
113
113
  }
114
114
  return out;
115
115
  }
116
- // The budget: 200,000 nodes and, by default, 20 seconds per goal; past either the goal is left `unknown`.
117
- export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + 20_000 }) {
116
+ // The budget: 200,000 nodes and, by default, a second per hypothesis (at least 20 seconds) per goal; past either the goal
117
+ // is left `unknown`. A goal with many instances (a law about two keys at its pairs) has that many more rows per leaf.
118
+ export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
118
119
  if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
119
120
  return { stuck: 'unknown', path };
120
121
  const rows = leafConstraints(goal, path);
@@ -223,11 +224,17 @@ function fractional(point, path) {
223
224
  return undefined;
224
225
  }
225
226
  // The keys a proof assumes the every-key laws at: every key the step and the laws read, and the fresh key of the goal.
227
+ // A key bound by a quantified flag or a write at every key isn't one: those reads are at every key already.
226
228
  function readKeys(m, laws) {
227
229
  const out = new Map();
230
+ const bound = new Set([...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ? [c.key] : [])), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : []))]);
231
+ const free = (e) => !JSON.stringify(e).match(/"name":"([^"]+)"/g)?.some(n => bound.has(n.slice(8, -1)));
228
232
  const inE = (e) => {
229
- if (e.op === 'get')
230
- (out.set(JSON.stringify(e.key), e.key), inE(e.key));
233
+ if (e.op === 'get') {
234
+ if (free(e.key))
235
+ out.set(JSON.stringify(e.key), e.key);
236
+ inE(e.key);
237
+ }
231
238
  else if (e.op === 'if')
232
239
  (inC(e.c), inE(e.then), inE(e.else));
233
240
  else if (e.op === 'mul')
@@ -235,20 +242,208 @@ function readKeys(m, laws) {
235
242
  else if ('a' in e)
236
243
  (inE(e.a), inE(e.b));
237
244
  };
238
- const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
245
+ const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
239
246
  Object.values(m.step.state ?? {}).forEach(inE);
240
247
  Object.values(m.step.flags ?? {}).forEach(inC);
241
- for (const w of Object.values(m.step.maps ?? {}))
242
- (out.set(JSON.stringify(w.key), w.key), inE(w.key), inE(w.value));
248
+ for (const w of Object.values(m.step.maps ?? {})) {
249
+ if (!('each' in w)) {
250
+ out.set(JSON.stringify(w.key), w.key);
251
+ inE(w.key);
252
+ }
253
+ inE(w.value);
254
+ }
243
255
  for (const l of laws.filter(l => !l.forall))
244
256
  inC(l.holds);
245
257
  return [...out.values()];
246
258
  }
259
+ const shifts = (m) => Object.values(m.step.maps ?? {}).some(w => 'each' in w && !plainKey(w.value, w.each));
260
+ // The keys some goals read a map at.
261
+ function mapKeys(goals) {
262
+ const out = new Map();
263
+ const inE = (e) => {
264
+ if (e.op === 'get') {
265
+ out.set(JSON.stringify(e.key), e.key);
266
+ inE(e.key);
267
+ }
268
+ else if (e.op === 'if')
269
+ (inC(e.c), inE(e.then), inE(e.else));
270
+ else if (e.op === 'mul')
271
+ inE(e.a);
272
+ else if ('a' in e)
273
+ (inE(e.a), inE(e.b));
274
+ };
275
+ const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
276
+ for (const g of goals)
277
+ g.hyps.forEach(inC);
278
+ return [...out.values()];
279
+ }
280
+ // Instances with each every-key law also at these keys. A law about two keys or more goes at tuples that relate the
281
+ // goal's own keys (`focus`: the skolems of the law it proves, or a step law's keys) to each other and to the keys that
282
+ // read none of them: at the i-th place, a key that reads the i-th focus alone (the first, past the focus there is), and
283
+ // at most one place a key that reads no focus.
284
+ const withReads = (laws, inst, keys, focus) => ({ ...inst, ...Object.fromEntries(laws.filter(l => l.forall).map(l => {
285
+ const had = inst[l.name] ?? [];
286
+ if (l.keys?.length) {
287
+ const n = l.keys.length + 1;
288
+ const reads = (e, x) => x !== undefined && JSON.stringify(e).includes(`"name":${JSON.stringify(x)}`);
289
+ const own = (i) => { const f = focus[i] ?? focus[0]; return keys.filter(e => reads(e, f) && focus.every(g => g === f || !reads(e, g))); };
290
+ const R = keys.filter(e => !focus.some(f => reads(e, f)));
291
+ const out = new Map();
292
+ for (let i = 0; i + n <= had.length; i += n)
293
+ out.set(JSON.stringify(had.slice(i, i + n)), had.slice(i, i + n));
294
+ const go = (i, tuple, rest) => {
295
+ if (i === n) {
296
+ out.set(JSON.stringify(tuple), tuple);
297
+ return;
298
+ }
299
+ for (const e of own(i))
300
+ go(i + 1, [...tuple, e], rest);
301
+ if (!rest)
302
+ for (const e of R)
303
+ go(i + 1, [...tuple, e], true);
304
+ };
305
+ go(0, [], false);
306
+ return [l.name, [...out.values()].flat()];
307
+ }
308
+ const seen = new Set(had.map(e => JSON.stringify(e)));
309
+ return [l.name, [...had, ...keys.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
310
+ })) });
311
+ // A law about every n keys at every n-tuple of the keys (flattened, as Instances keeps tuples).
312
+ const tuples = (keys, n) => (n === 0 ? [] : n === 1 ? keys : keys.flatMap(a => { const rest = tuples(keys, n - 1); return Array.from({ length: rest.length / (n - 1) }, (_, t) => [a, ...rest.slice(t * (n - 1), (t + 1) * (n - 1))]).flat(); }));
313
+ // The keys some goals read a function at.
314
+ function fnKeys(m, goals) {
315
+ const fns = Object.keys(m.fns ?? {}), out = new Map();
316
+ const inE = (e) => {
317
+ if (e.op === 'get') {
318
+ if (fns.includes(e.map))
319
+ out.set(JSON.stringify(e.key), e.key);
320
+ inE(e.key);
321
+ }
322
+ else if (e.op === 'if')
323
+ (inC(e.c), inE(e.then), inE(e.else));
324
+ else if (e.op === 'mul')
325
+ inE(e.a);
326
+ else if ('a' in e)
327
+ (inE(e.a), inE(e.b));
328
+ };
329
+ const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
330
+ for (const g of goals)
331
+ g.hyps.forEach(inC);
332
+ return [...out.values()];
333
+ }
334
+ // Instances with each fact about every key assumed where it reads a function at one of these keys: its key at each
335
+ // value that makes one of its reads one of these (by matching: a function of several arguments reads a paired key), and
336
+ // at the keys themselves.
337
+ const withFacts = (m, inst, keys) => ({ ...inst, ...Object.fromEntries((m.facts ?? []).filter(f => f.forall).map(f => {
338
+ if (f.keys?.length)
339
+ return [f.name, tuples(keys, f.keys.length + 1)];
340
+ const had = inst[f.name] ?? [], seen = new Set(had.map(e => JSON.stringify(e)));
341
+ const reads = fnKeys(m, [{ name: '', hyps: [f.holds] }]);
342
+ const at = [...keys, ...reads.flatMap(pattern => keys.flatMap(key => { const b = match(pattern, key, f.forall); return b ? [b] : []; }))];
343
+ return [f.name, [...had, ...at.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
344
+ })) });
345
+ // The value of x that makes pattern the term, if one does (structurally).
346
+ function match(pattern, term, x) {
347
+ let bound;
348
+ const go = (p, t) => {
349
+ if (p.op === 'var' && p.name === x) {
350
+ if (bound)
351
+ return JSON.stringify(bound) === JSON.stringify(t);
352
+ bound = t;
353
+ return true;
354
+ }
355
+ if (p.op !== t.op)
356
+ return false;
357
+ const pe = Object.entries(p), te = t;
358
+ return pe.every(([k, v]) => k === 'op' || (typeof v === 'object' && v !== null ? (Array.isArray(v) ? Array.isArray(te[k]) && te[k].length === v.length && v.every((y, i) => go(y, te[k][i])) : go(v, te[k])) : v === te[k]));
359
+ };
360
+ return go(pattern, term) ? bound : undefined;
361
+ }
362
+ // Each quantified flag's witness w?f: a key where its condition fails, when it does; every-key facts are wanted there too.
363
+ const witnesses = (m) => Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [{ op: 'var', name: `w?${f}` }] : []));
364
+ // The keys each quantified flag is assumed at (instances under ?f).
365
+ const flagInstances = (m, keys) => Object.fromEntries(Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [[`?${f}`, keys]] : [])));
366
+ const someKeys = (c, positive = true) => c.op === 'all' ? [...(positive ? [] : [c]), ...someKeys(c.c, positive)] : c.op === 'not' ? someKeys(c.x, !positive) : c.op === 'and' || c.op === 'or' ? c.xs.flatMap(y => someKeys(y, positive)) : [];
367
+ function atWitness(c, picks, positive = true) {
368
+ switch (c.op) {
369
+ case 'all': return !positive && picks[c.key] ? atWitness(substVar(c.c, c.key, picks[c.key]), picks, positive) : { ...c, c: atWitness(c.c, picks, positive) };
370
+ case 'not': return { op: 'not', x: atWitness(c.x, picks, !positive) };
371
+ case 'and':
372
+ case 'or': return { op: c.op, xs: c.xs.map(y => atWitness(y, picks, positive)) };
373
+ case 'flag':
374
+ case '<':
375
+ case '<=':
376
+ case '=': return c;
377
+ }
378
+ }
379
+ function substVar(c, name, by) {
380
+ const e = (t) => {
381
+ switch (t.op) {
382
+ case 'var': return t.name === name ? by : t;
383
+ case 'const': return t;
384
+ case 'mul': return { ...t, a: e(t.a) };
385
+ case 'if': return { op: 'if', c: substVar(t.c, name, by), then: e(t.then), else: e(t.else) };
386
+ case 'get': return { ...t, key: e(t.key) };
387
+ case 'add':
388
+ case 'sub':
389
+ case 'min':
390
+ case 'max':
391
+ case 'prod': return { ...t, a: e(t.a), b: e(t.b) };
392
+ }
393
+ };
394
+ return c.op === 'flag' ? c : c.op === 'all' ? { ...c, c: substVar(c.c, name, by) } : c.op === 'not' ? { op: 'not', x: substVar(c.x, name, by) } : c.op === 'and' || c.op === 'or' ? { op: c.op, xs: c.xs.map(y => substVar(y, name, by)) } : { ...c, a: e(c.a), b: e(c.b) };
395
+ }
396
+ const candidates = (m, names) => {
397
+ const vars = [...new Set([...names, ...m.params, ...Object.keys(m.state)])];
398
+ return [{ op: 'const', value: 0 }, ...vars.map((name) => ({ op: 'var', name })), ...Object.keys(m.maps ?? {}).flatMap(map => vars.map((name) => ({ op: 'get', map, key: { op: 'var', name } })))];
399
+ };
400
+ // Witnesses for every some-key in `items`, chosen in order: each the first candidate under which `proves` accepts the
401
+ // items whose some-keys are all named so far. Undefined, with the some-key's law, if none does.
402
+ function chooseWitnesses(items, body, names, m, proves, rebuild) {
403
+ const picks = {};
404
+ for (const t of items)
405
+ for (const q of someKeys(body(t)))
406
+ if (q.witness)
407
+ picks[q.key] = q.witness;
408
+ const resolved = (all) => items.filter(t => all || someKeys(body(t)).every(q => q.key in picks)).map(t => (someKeys(body(t)).length ? rebuild(t, atWitness(body(t), picks)) : t));
409
+ for (const t of items)
410
+ for (const q of someKeys(body(t))) {
411
+ if (q.key in picks)
412
+ continue;
413
+ const found = candidates(m, names(t)).find(w => { picks[q.key] = w; return proves(resolved(false)); });
414
+ if (!found) {
415
+ delete picks[q.key];
416
+ return { stuck: `no witness found for some key in ${t.name}: name one (someKey(f, witness))` };
417
+ }
418
+ }
419
+ return { items: resolved(true) };
420
+ }
247
421
  export function proveLaws(m, laws) {
248
- const keys = readKeys(m, laws);
422
+ if (!laws.some(l => someKeys(l.holds).length))
423
+ return proveLawsAsGiven(m, laws);
424
+ const out = chooseWitnesses(laws, l => l.holds, l => (l.forall ? [l.forall] : []), m, ls => { const r = proveLawsAsGiven(m, ls); return r.proven && r.checked; }, (l, holds) => ({ ...l, holds, stated: l.stated ?? l.holds }));
425
+ if ('stuck' in out)
426
+ return { proven: false, nodes: 0, leaves: 0, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
427
+ return { ...proveLawsAsGiven(m, out.items), laws: out.items };
428
+ }
429
+ function proveLawsAsGiven(m, laws) {
430
+ const keys = [...readKeys(m, laws), ...witnesses(m)];
249
431
  const instances = {};
250
- for (const law of laws)
251
- instances[law.name] = Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : [])]]));
432
+ for (const law of laws) {
433
+ const at = [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : []), ...(law.keys ?? []).map((name) => ({ op: 'var', name }))];
434
+ instances[law.name] = { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : at])), ...flagInstances(m, at) };
435
+ }
436
+ // The facts about functions, at every key each law's goals read a function at (found from the goals, twice round).
437
+ for (let round = 0; round < 2 && m.facts?.length; round++) {
438
+ const goals = obligations(m, laws, instances);
439
+ laws.forEach((law, i) => { instances[law.name] = withFacts(m, instances[law.name], fnKeys(m, goals.slice(2 * i, 2 * i + 2))); });
440
+ }
441
+ // Where positions shift (a write at every key reading its key at k ∸ 1 or k + 1) or a law is about two keys, the
442
+ // every-key laws also at each key the goals read a map at, once round.
443
+ if (shifts(m) || laws.some(l => l.keys?.length)) {
444
+ const goals = obligations(m, laws, instances);
445
+ laws.forEach((law, i) => { instances[law.name] = withReads(laws, instances[law.name], mapKeys(goals.slice(2 * i, 2 * i + 2)), [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])]); });
446
+ }
252
447
  const trees = {};
253
448
  let nodes = 0, leaves = 0;
254
449
  for (const goal of obligations(m, laws, instances)) {
@@ -262,19 +457,39 @@ export function proveLaws(m, laws) {
262
457
  const proof = { instances, trees };
263
458
  return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
264
459
  }
265
- // Step laws (kernel.ts's stepLawObligations): the laws proven first (they are assumed), then one goal per step law, the
266
- // every-key laws assumed at every key the step, the laws and the step laws read.
267
- export function proveStepLaws(m, laws, stepLaws) {
268
- const base = proveLaws(m, laws);
460
+ export function proveStepLaws(m, given, stepLaws) {
461
+ const base = proveLaws(m, given);
269
462
  if (!base.proven || !base.proof)
270
463
  return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
464
+ const laws = base.laws ?? given;
465
+ if (stepLaws.some(sl => someKeys(sl.then).length)) {
466
+ // Each step law's witnesses on its own (a step law assumes no other), the request and the state after among them.
467
+ const plain = m.history ? historyMachine(m).machine : m, after = Object.keys(plain.state).map(n => `${n}'`);
468
+ const out = chooseWitnesses(stepLaws, sl => sl.then, sl => [...(sl.forall ? [sl.forall] : []), ...plain.inputs, ...after], plain, sls => sls.every(sl => { const r = proveStepLawsAsGiven(m, laws, [sl], base); return r.proven && r.checked; }), (sl, then) => ({ ...sl, then }));
469
+ if ('stuck' in out)
470
+ return { proven: false, nodes: base.nodes, leaves: base.leaves, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
471
+ return { ...proveStepLawsAsGiven(m, laws, out.items, base), ...(base.laws ? { laws } : {}), stepLaws: out.items };
472
+ }
473
+ return { ...proveStepLawsAsGiven(m, laws, stepLaws, base), ...(base.laws ? { laws } : {}) };
474
+ }
475
+ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
476
+ if (!base.proof)
477
+ return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
271
478
  const plain = m.history ? historyMachine(m).machine : m;
272
- const keys = [...readKeys(plain, laws), ...stepLaws.flatMap(sl => [...readKeysC(sl.then), ...(sl.when ? readKeysC(sl.when) : [])])];
273
- const instances = Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, keys]));
479
+ const keys = [...readKeys(plain, laws), ...witnesses(plain), ...stepLaws.flatMap(sl => [sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC)),
480
+ ...stepLaws.flatMap(sl => [sl.forall, ...(sl.keys ?? [])].flatMap(key => (key ? [{ op: 'var', name: key }] : [])))];
481
+ let instances = { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : keys])), ...flagInstances(plain, keys) };
482
+ for (let round = 0; round < 2 && plain.facts?.length; round++)
483
+ instances = withFacts(plain, instances, fnKeys(plain, stepLaws.flatMap(sl => stepLawObligations(m, laws, instances, sl))));
484
+ // Where positions shift or a law is about two keys, each step law gets its own instances, from its own goal.
485
+ const perStep = {};
486
+ if (shifts(plain) || laws.some(l => l.keys?.length))
487
+ for (const sl of stepLaws)
488
+ perStep[sl.name] = withReads(laws, instances, mapKeys(stepLawObligations(m, laws, instances, sl)), [sl.forall, ...(sl.keys ?? [])].flatMap(k => (k ? [k] : [])));
274
489
  const trees = {};
275
490
  let nodes = base.nodes, leaves = base.leaves;
276
491
  for (const sl of stepLaws)
277
- for (const goal of stepLawObligations(m, laws, instances, sl)) {
492
+ for (const goal of stepLawObligations(m, laws, perStep[sl.name] ?? instances, sl)) {
278
493
  const out = grow(goal);
279
494
  if (!('tree' in out))
280
495
  return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
@@ -282,9 +497,53 @@ export function proveStepLaws(m, laws, stepLaws) {
282
497
  nodes += out.nodes;
283
498
  leaves += countLeaves(out.tree);
284
499
  }
285
- const proof = { laws: base.proof, instances, trees };
500
+ const proof = { laws: base.proof, instances, ...(Object.keys(perStep).length ? { perStep } : {}), trees };
286
501
  return { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
287
502
  }
503
+ // Progress (kernel.ts's progressObligations): the laws proven first, then each goal, the every-key laws assumed at every
504
+ // key the goals read a map at.
505
+ export function proveProgress(m, laws, p) {
506
+ const base = proveLaws(m, laws);
507
+ if (!base.proven || !base.checked)
508
+ return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
509
+ const ls = base.laws ?? laws;
510
+ let instances = { ...withReads(ls, {}, mapKeys(progressObligations(m, ls, {}, p)), []), ...flagInstances(m, mapKeys(progressObligations(m, ls, {}, p))) };
511
+ instances = { ...instances, ...withReads(ls, instances, mapKeys(progressObligations(m, ls, instances, p)), []) };
512
+ if (m.facts?.length)
513
+ instances = withFacts(m, instances, fnKeys(m, progressObligations(m, ls, instances, p)));
514
+ const trees = {};
515
+ let nodes = base.nodes, leaves = base.leaves;
516
+ for (const goal of progressObligations(m, ls, instances, p)) {
517
+ const out = grow(goal);
518
+ if (!('tree' in out))
519
+ return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
520
+ trees[goal.name] = out.tree;
521
+ nodes += out.nodes;
522
+ leaves += countLeaves(out.tree);
523
+ }
524
+ return { proven: true, nodes, leaves, checked: progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name])), instances, trees };
525
+ }
526
+ // Liveness (kernel.ts's livenessParts): its step laws, proven together. The claim needs no more (a run takes the
527
+ // request, so it is allowed); but a request never allowed would leave the claim about no run, so where a helpful request
528
+ // names every input a client sends and carries no maps, it is proven allowed in its region too.
529
+ export function proveLiveness(m, laws, l) {
530
+ const { stay, cover, helps } = livenessParts(m, l);
531
+ const a = proveStepLaws(m, laws, [stay, cover, ...helps]);
532
+ if (!a.proven || !a.checked)
533
+ return { proven: false, nodes: a.nodes, leaves: a.leaves, ...(a.failed ? { failed: a.failed } : {}), checked: false };
534
+ const plain = m.history ? historyMachine(m).machine : m;
535
+ let nodes = a.nodes, leaves = a.leaves;
536
+ for (const [i, h] of livenessHelpful(l).entries()) {
537
+ if (m.history || plain.inputMaps?.length || !plain.assume || !plain.inputs.every(inp => inp in h.request))
538
+ continue;
539
+ const b = proveProgress(plain, laws, { name: `${l.name}: helpful request ${i + 1} is allowed`, when: { op: 'and', xs: [l.from, { op: 'not', x: l.to }, h.when] }, then: trueC, request: h.request });
540
+ nodes += b.nodes;
541
+ leaves += b.leaves;
542
+ if (!b.proven || !b.checked)
543
+ return { proven: false, nodes, leaves, ...(b.failed ? { failed: b.failed } : {}), checked: false };
544
+ }
545
+ return { proven: true, checked: true, nodes, leaves, steps: a };
546
+ }
288
547
  // The key terms a condition reads maps at; a primed map's key is a key of the map before the step too.
289
548
  function readKeysC(c) {
290
549
  const out = [];
@@ -296,7 +555,7 @@ function readKeysC(c) {
296
555
  inE(e.a);
297
556
  else if ('a' in e)
298
557
  (inE(e.a), inE(e.b)); };
299
- const inC = (x) => (x.op === 'flag' ? undefined : x.op === 'not' ? inC(x.x) : x.op === 'and' || x.op === 'or' ? x.xs.forEach(inC) : (inE(x.a), inE(x.b)));
558
+ const inC = (x) => (x.op === 'flag' || x.op === 'all' ? undefined : x.op === 'not' ? inC(x.x) : x.op === 'and' || x.op === 'or' ? x.xs.forEach(inC) : (inE(x.a), inE(x.b)));
300
559
  inC(c);
301
560
  return out;
302
561
  }
@@ -315,7 +574,7 @@ export function concretize(goal, bound = 12) {
315
574
  (walkC(e.c), walkE(e.then), walkE(e.else));
316
575
  else if ('a' in e)
317
576
  (walkE(e.a), 'b' in e && walkE(e.b)); };
318
- const walkC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? walkC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(walkC) : (walkE(c.a), walkE(c.b)));
577
+ const walkC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? void (reads = true) : c.op === 'not' ? walkC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(walkC) : (walkE(c.a), walkE(c.b)));
319
578
  goal.hyps.forEach(walkC);
320
579
  if (reads || names.size > 4)
321
580
  return undefined;
@@ -334,7 +593,7 @@ export function concretize(goal, bound = 12) {
334
593
  }
335
594
  };
336
595
  const holds = (c, env) => c.op === 'not' ? !holds(c.x, env) : c.op === 'and' ? c.xs.every(y => holds(y, env)) : c.op === 'or' ? c.xs.some(y => holds(y, env))
337
- : c.op === 'flag' ? false : c.op === '<' ? ev(c.a, env) < ev(c.b, env) : c.op === '<=' ? ev(c.a, env) <= ev(c.b, env) : ev(c.a, env) === ev(c.b, env);
596
+ : c.op === 'flag' || c.op === 'all' ? false : c.op === '<' ? ev(c.a, env) < ev(c.b, env) : c.op === '<=' ? ev(c.a, env) <= ev(c.b, env) : ev(c.a, env) === ev(c.b, env);
338
597
  const vars = [...names];
339
598
  const total = (bound + 1) ** vars.length;
340
599
  for (let i = 0; i < total; i++) {
@@ -345,3 +604,18 @@ export function concretize(goal, bound = 12) {
345
604
  }
346
605
  return undefined;
347
606
  }
607
+ // A goal built from its instances (an exporter's goal, shaped as its Lean twin builds it): the every-key laws, the
608
+ // quantified flags and the facts assumed at every key it reads a map or function at, then a tree, checked.
609
+ export function proveBuilt(m, laws, build) {
610
+ let instances = {};
611
+ for (let round = 0; round < 2; round++) {
612
+ const g = build(instances), keys = mapKeys([g]);
613
+ instances = { ...withReads(laws, instances, keys, []), ...flagInstances(m, keys) };
614
+ if (m.facts?.length)
615
+ instances = withFacts(m, instances, fnKeys(m, [build(instances)]));
616
+ }
617
+ const goal = build(instances), out = grow(goal);
618
+ if (!('tree' in out) || !check(goal, out.tree))
619
+ return { proven: false };
620
+ return { proven: true, instances, goal, tree: out.tree };
621
+ }
@@ -0,0 +1,132 @@
1
+ import type { C, E, Law, Machine } from '../core/machine.js';
2
+ import { type Step } from '../core/solver.js';
3
+ import { type Fold, type Goal, type Instances, type Liveness, type Loop, type Path, type StepLaw, type Tree } from '../kernel/kernel.js';
4
+ import { leanLibrary } from './library.js';
5
+ export { leanLibrary };
6
+ export type Exported = {
7
+ name: string;
8
+ goal: Goal;
9
+ tree: Tree;
10
+ };
11
+ export declare const int: (v: number) => string;
12
+ export declare const list: (xs: string[]) => string;
13
+ export declare function leaves(t: Tree, path: Path, visit: (t: Tree & {
14
+ leaf: Step[];
15
+ products?: [number, number][];
16
+ }, path: Path) => void): void;
17
+ export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[]): {
18
+ tab: string;
19
+ hyps: string;
20
+ tree: string;
21
+ cols: string[];
22
+ c: (t: C) => string;
23
+ e: (t: E) => string;
24
+ };
25
+ export declare function goalToLean(ex: Exported, i: number, tamper?: boolean, dropStep?: boolean): string;
26
+ export declare function machineToLean(id: string, m: Machine, laws: Law[], corollary?: string): string;
27
+ export declare const TRUE = ".le (.const 0) (.const 0)";
28
+ export declare const grown: (g: Goal) => Tree;
29
+ export declare function loopToLean(id: string, l: Loop, laws: Law[], rank: E | E[], tamper?: {
30
+ what: string;
31
+ loop: Loop;
32
+ }): string;
33
+ export declare function foldToLean(id: string, f: Fold, laws: Law[], extra?: string): string;
34
+ export declare function readable(id: string, laws: Law[], vars: string[], maps: string[], reach: {
35
+ binders: string;
36
+ hyp: string;
37
+ call: string;
38
+ }): string;
39
+ export type ProgressCase = {
40
+ id: string;
41
+ name: string;
42
+ when: C;
43
+ then: C;
44
+ request: Record<string, E>;
45
+ instances: Instances;
46
+ tamper?: {
47
+ what: string;
48
+ when: C;
49
+ };
50
+ };
51
+ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts?: {
52
+ corollary?: string;
53
+ tamper?: {
54
+ what: string;
55
+ machine: Machine;
56
+ };
57
+ progress?: ProgressCase[];
58
+ free?: string[];
59
+ stepLaws?: StepLaw[];
60
+ liveness?: Liveness[];
61
+ ghosts?: string[];
62
+ after?: (ix: (name: string) => number) => string;
63
+ refined?: {
64
+ impl: Machine;
65
+ spec: Machine;
66
+ wit: Record<string, E>;
67
+ stutter: C;
68
+ specPre: number;
69
+ };
70
+ }): string;
71
+ export declare const hasSets: (m: Machine) => boolean;
72
+ export declare function smachineToLean(id: string, m: Machine, laws: Law[], opts?: {
73
+ stepLaws?: StepLaw[];
74
+ liveness?: Liveness[];
75
+ tamper?: {
76
+ what: string;
77
+ machine: Machine;
78
+ };
79
+ simp?: string[];
80
+ free?: string[];
81
+ refined?: {
82
+ impl: Machine;
83
+ spec: Machine;
84
+ wit: Record<string, E>;
85
+ stutter: C;
86
+ specPre: number;
87
+ };
88
+ }): string;
89
+ export declare function hmachineToLean(id: string, pruned: Machine, laws: Law[], tampers?: {
90
+ what: string;
91
+ relations: {
92
+ when: C;
93
+ holds: C;
94
+ }[];
95
+ withRecordTrees: boolean;
96
+ }[], corollary?: (ix: (name: string) => number) => string): string;
97
+ export type LeanItem = {
98
+ id: string;
99
+ machine: Machine;
100
+ laws: Law[];
101
+ stepLaws?: StepLaw[];
102
+ liveness?: Liveness[];
103
+ ghosts?: string[];
104
+ lean?: {
105
+ defs?: string;
106
+ simp?: string[];
107
+ after?: (ix: (name: string) => number) => string;
108
+ };
109
+ } | {
110
+ id: string;
111
+ refinement: {
112
+ impl: Machine;
113
+ spec: Machine;
114
+ relation: Law[];
115
+ witness: Record<string, E>;
116
+ stutter?: C;
117
+ };
118
+ } | {
119
+ id: string;
120
+ loop: Loop;
121
+ rank: E | E[];
122
+ laws?: Law[];
123
+ } | {
124
+ id: string;
125
+ goal: Goal;
126
+ };
127
+ export declare function toLean(items: LeanItem[], opts?: {
128
+ title?: string;
129
+ }): string;
130
+ export declare function leanProject(file: string, opts?: {
131
+ name?: string;
132
+ }): Record<string, string>;