gdp-wired 0.20.0 → 0.21.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.
@@ -369,17 +369,30 @@ theorem ${id}_laws : ∀ maps ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t,
369
369
  // abbreviation for its column), proven from the machine's theorem by unfolding the law's own definition. The statement
370
370
  // is what a Lean development cites; the column numbers stay inside the namespace.
371
371
  // `have`: the line that gives each law as a hypothesis hl (an SEntry's Holds by default; a cyclic proof's, holdsOver).
372
+ // A law with "there exists" (core/exists.ts, `ex` on the law the kernel proved) is stated as the existential it is:
373
+ // `<id>_someN`, ∃ w1 .. wn, P with the bound naturals set to them (updAll, Ghost.lean), from the law proven with its
374
+ // witness terms in place (some_of_subst) or over the machine's ghosts at every reachable state of the machine without
375
+ // them (some_of_ghost); and `<id>.lawN` the same over named variables, each bound natural as w1, w2, ... (`lit`: the
376
+ // condition printer, for P's literal).
372
377
  export function readable(id, laws, vars, maps, reach) {
373
378
  for (const name of [...vars, ...maps])
374
379
  if (name === 'ρ' || name === 'μ' || name.includes('«') || name.includes('»'))
375
380
  throw new Error(`${id}: the name ${name} can't be a Lean abbreviation here`);
376
381
  const q = (name) => `«${name}»`;
377
- const statement = (law) => {
382
+ // Division and remainder by a constant (div$k, mod$k): Lean's x / k and x % k, under the hypothesis that the map is
383
+ // that function (what the facts' theorem, <id>_facts_hold, takes too), rewritten into the statement.
384
+ const arithOf = (map) => { const m = /^(div|mod)\$(\d+)$/.exec(map); return m ? { op: m[1] === 'div' ? '/' : '%', k: Number(m[2]) } : undefined; };
385
+ const arithIn = (law) => [...new Set([...JSON.stringify(law.holds).matchAll(/"op":"get","map":"((?:div|mod)\$\d+)"/g)].map(m => m[1]))].filter(mp => maps.includes(mp)).sort();
386
+ const hypName = (map) => `h_${map.replace('$', '')}`;
387
+ const arithBinders = (law) => arithIn(law).map(mp => ` (${hypName(mp)} : μ ${q(mp)} = fun x => x ${arithOf(mp).op} ${arithOf(mp).k})`).join('');
388
+ const keyName = (law, name) => { const j = (law.keys ?? []).indexOf(name); return name === law.forall ? 'k' : j >= 0 ? `k${j + 2}` : undefined; };
389
+ const statement = (law, plain = true) => {
390
+ const bound = law.ex?.vars ?? [];
378
391
  const t = (e) => {
379
392
  switch (e.op) {
380
393
  case 'var': {
381
- const j = (law.keys ?? []).indexOf(e.name);
382
- return e.name === law.forall ? 'k' : j >= 0 ? `k${j + 2}` : `ρ ${q(e.name)}`;
394
+ const b = bound.indexOf(e.name);
395
+ return b >= 0 ? `w${b + 1}` : keyName(law, e.name) ?? `ρ ${q(e.name)}`;
383
396
  }
384
397
  case 'const': return String(e.value);
385
398
  case 'add': return `(${t(e.a)} + ${t(e.b)})`;
@@ -389,7 +402,10 @@ export function readable(id, laws, vars, maps, reach) {
389
402
  case 'min':
390
403
  case 'max': return `(${e.op} ${t(e.a)} ${t(e.b)})`;
391
404
  case 'if': return `(if ${p(e.c)} then ${t(e.then)} else ${t(e.else)})`;
392
- case 'get': return `(μ ${q(e.map)} (${t(e.key)}))`;
405
+ case 'get': {
406
+ const a = plain ? arithOf(e.map) : undefined;
407
+ return a ? `(${t(e.key)} ${a.op} ${a.k})` : `(μ ${q(e.map)} (${t(e.key)}))`;
408
+ }
393
409
  }
394
410
  };
395
411
  const p = (c) => {
@@ -404,16 +420,170 @@ export function readable(id, laws, vars, maps, reach) {
404
420
  case 'all': throw new Error(`${id}: a law has no quantifier inside`);
405
421
  }
406
422
  };
407
- return law.keys?.length ? `∀ k ${law.keys.map((_, j) => `k${j + 2}`).join(' ')} : Nat, ${p(law.holds)}` : law.forall ? `∀ k : Nat, ${p(law.holds)}` : p(law.holds);
423
+ return quantified(law, law.ex ? `∃ ${bound.map((_, i) => `w${i + 1}`).join(' ')} : Nat, ${p(law.ex.body)}` : p(law.holds));
424
+ };
425
+ const haveOf = (j) => (reach.have ? reach.have(j) : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`);
426
+ // The existential of law j (one with `ex`): its theorem over the machine's own variables, then over named ones.
427
+ const someOf = (law, j) => {
428
+ const ex = law.ex, lit = reach.lit, eLit = reach.eLit;
429
+ if (!lit || !eLit)
430
+ throw new Error(`${id}: ${law.name} has "there exists" and no printer for its condition`);
431
+ if (law.keys?.length || (law.forall && ex.ghost))
432
+ throw new Error(`${id}: ${law.name}: "there exists" past one key, or through a ghost under a key, isn't exported`);
433
+ const ix = (name) => { const i = vars.indexOf(name); if (i < 0)
434
+ throw new Error(`${id}: no variable for ${name}`); return i; };
435
+ const ws = ex.vars.map((_, i) => `w${i + 1}`), at = (env, vals) => `updAll ${env} [${ex.vars.map((n, i) => `(${ix(n)}, ${vals[i]})`).join(', ')}]`;
436
+ const P = `(${lit(ex.body)})`, key = law.forall ? ix(law.forall) : undefined;
437
+ const sigma = ex.sub ? `[${ex.vars.map(n => `(${ix(n)}, ${eLit(ex.sub[n])})`).join(', ')}]` : '';
438
+ const machine = ex.ghost ? `${id}_ghosted.orig` : `${id}_machine`;
439
+ const head = `theorem some${j} : ∀ ρ μ, SReach ${machine} ρ μ → ${key !== undefined ? '∀ k : Nat, ' : ''}∃ ${ws.join(' ')} : Nat, evalC (${at(key !== undefined ? `(upd ρ ${key} k)` : 'ρ', ws)}) μ ${P} = true`;
440
+ const proof = ex.ghost
441
+ ? ` intro ρ μ h
442
+ obtain ⟨ρg, hg⟩ := some_of_ghost ${id}_ghosted ${id}_ghostOk [${ex.vars.map(ix).join(', ')}] ${P} (by ${checkBy}) (fun ρ μ h => by
443
+ ${haveOf(j)}
444
+ exact hl) ρ μ h
445
+ exact ⟨${ex.vars.map(n => `ρg ${ix(n)}`).join(', ')}, hg⟩`
446
+ : ` intro ρ μ h${key !== undefined ? ' k' : ''}
447
+ ${haveOf(j)}
448
+ have hs : substC ${sigma} ${P} = ${id}_l${j}.holds := beqC_eq (by ${checkBy})
449
+ have hq : evalC ${key !== undefined ? `(upd ρ ${key} k)` : 'ρ'} μ (substC ${sigma} ${P}) = true := by rw [hs]; exact hl${key !== undefined ? ' k' : ''}
450
+ exact ⟨${ex.vars.map(n => `applyS ${sigma} ${key !== undefined ? `(upd ρ ${key} k)` : 'ρ'} μ ${ix(n)}`).join(', ')}, some_of_subst ${sigma} ${P} _ μ hq⟩`;
451
+ const plain = JSON.stringify(ex.body).includes('"op":"if"') ? `-- ${law.name}: its terms read an if, so no plain statement here (some${j} holds it)` : `/-- ${law.name.replace(/-\//g, '- /')} -/
452
+ theorem law${j} ${reach.binders} (h : SReach ${machine} ρ μ) : ${statement(law)} := by
453
+ ${key !== undefined ? 'intro k\n ' : ''}obtain ⟨${ws.join(', ')}, hg⟩ := some${j} ρ μ h${key !== undefined ? ' k' : ''}
454
+ exact ⟨${ws.join(', ')}, (evalC_iff (${at(key !== undefined ? `(upd ρ ${key} k)` : 'ρ', ws)}) μ ${P}).mp hg⟩`;
455
+ return `-- ${law.name}: there exists, proven ${ex.ghost ? 'through ghosts the machine carries' : 'with its witness terms in place'}
456
+ ${head} := by
457
+ ${proof}
458
+ ${plain}`;
408
459
  };
460
+ const quantified = (law, body) => (law.keys?.length ? `∀ k ${law.keys.map((_, j) => `k${j + 2}`).join(' ')} : Nat, ${body}` : law.forall ? `∀ k : Nat, ${body}` : body);
461
+ // Where the source says integer (a variable x beside x$neg: the value x − x$neg, core/dsl.ts's Int), the law again
462
+ // over Int: each comparison's two sides moved to one, each integer's two naturals read together as its value, x div k
463
+ // and x mod k of an integer as Lean's Int / and % (pos + (k − 1)·neg's quotient is the value's plus neg, its remainder
464
+ // the value's). Proven from the law over the naturals by omega. Only for laws whose terms are sums (no if, min, max,
465
+ // truncation or product): those have no plain statement over Int to give.
466
+ const ints = vars.filter(x => vars.includes(`${x}$neg`));
467
+ const intStatement = (law) => {
468
+ const plus = (a, b, f = 1) => { const out = new Map([...a].map(([key, v]) => [key, { ...v }])); for (const [key, v] of b) {
469
+ const w = out.get(key);
470
+ if (w)
471
+ w.c += f * v.c;
472
+ else
473
+ out.set(key, { c: f * v.c, show: v.show });
474
+ } return out; };
475
+ const natShow = (e) => { const r = lin(e); if (!r)
476
+ return undefined; return statementOfE(e); };
477
+ const statementOfE = (e) => { const l2 = { ...law, holds: { op: '=', a: e, b: e } }; return statement({ ...l2, forall: undefined, keys: undefined }).split(' = ')[0]; };
478
+ const value = (x) => `((ρ ${q(x)} : Int) - ρ ${q(`${x}$neg`)})`;
479
+ // An integer's pos + (k − 1)·neg, read back as the integer.
480
+ const intKey = (key, k) => {
481
+ const l = lin(key);
482
+ if (!l)
483
+ return undefined;
484
+ const terms = [...l].filter(([, v]) => v.c !== 0);
485
+ const x = ints.find(i => l.get(`v:${i}`)?.c === 1);
486
+ return x && terms.length === (k > 1 ? 2 : 1) && (k === 1 || l.get(`v:${x}$neg`)?.c === k - 1) && !l.has('1') ? x : undefined;
487
+ };
488
+ const lin = (e) => {
489
+ switch (e.op) {
490
+ case 'var': {
491
+ const kn = keyName(law, e.name);
492
+ return new Map([[`v:${e.name}`, { c: 1, show: kn ? `(${kn} : Int)` : `(ρ ${q(e.name)} : Int)` }]]);
493
+ }
494
+ case 'const': return e.value ? new Map([['1', { c: e.value, show: '' }]]) : new Map();
495
+ case 'add': {
496
+ const a = lin(e.a), b = lin(e.b);
497
+ return a && b && plus(a, b);
498
+ }
499
+ case 'mul': {
500
+ const a = lin(e.a);
501
+ return a && new Map([...a].map(([key, v]) => [key, { c: v.c * e.k, show: v.show }]));
502
+ }
503
+ case 'get': {
504
+ const a = arithOf(e.map), x = a ? intKey(e.key, a.k) : undefined;
505
+ if (a && x)
506
+ return a.op === '%' ? new Map([[`mod:${a.k}:${x}`, { c: 1, show: `(${value(x)} % ${a.k})` }]]) : new Map([[`div:${a.k}:${x}`, { c: 1, show: `(${value(x)} / ${a.k})` }], [`v:${x}$neg`, { c: 1, show: `(ρ ${q(`${x}$neg`)} : Int)` }]]);
507
+ const shown = natShow(e.key) === undefined ? undefined : statementOfE(e);
508
+ return shown === undefined ? undefined : new Map([[`get:${shown}`, { c: 1, show: `(${shown} : Int)` }]]);
509
+ }
510
+ case 'sub':
511
+ case 'min':
512
+ case 'max':
513
+ case 'prod':
514
+ case 'if': return undefined;
515
+ }
516
+ };
517
+ const side = (ts) => (ts.length ? ts.map(([show, c]) => (c === 1 ? show : `${c} * ${show}`)).join(' + ') : '0');
518
+ const cmp = (c) => {
519
+ const a = lin(c.a), b = lin(c.b);
520
+ if (!a || !b)
521
+ return undefined;
522
+ const l = plus(a, b, -1);
523
+ // Each integer's two naturals together: c·x − c·x$neg is c times its value.
524
+ for (const x of ints) {
525
+ const pos = l.get(`v:${x}`), neg = l.get(`v:${x}$neg`);
526
+ if (pos && neg && pos.c === -neg.c && pos.c !== 0) {
527
+ l.delete(`v:${x}`);
528
+ l.delete(`v:${x}$neg`);
529
+ l.set(`int:${x}`, { c: pos.c, show: value(x) });
530
+ }
531
+ }
532
+ const k0 = l.get('1')?.c ?? 0;
533
+ l.delete('1');
534
+ const terms = [...l.values()].filter(v => v.c !== 0);
535
+ const left = [...terms.filter(v => v.c > 0).map(v => [v.show, v.c]), ...(k0 > 0 ? [[String(k0), 1]] : [])];
536
+ const right = [...terms.filter(v => v.c < 0).map(v => [v.show, -v.c]), ...(k0 < 0 ? [[String(-k0), 1]] : [])];
537
+ return `${side(left)} ${c.op === '<=' ? '≤' : c.op} ${side(right)}`;
538
+ };
539
+ const p = (c) => {
540
+ switch (c.op) {
541
+ case '<=':
542
+ case '<':
543
+ case '=': return cmp(c);
544
+ case 'and':
545
+ case 'or': {
546
+ const xs = c.xs.map(p);
547
+ return xs.some(x => x === undefined) ? undefined : xs.length ? `(${xs.join(c.op === 'and' ? ' ∧ ' : ' ∨ ')})` : c.op === 'and' ? 'True' : 'False';
548
+ }
549
+ case 'not': {
550
+ const x = p(c.x);
551
+ return x === undefined ? undefined : `¬(${x})`;
552
+ }
553
+ case 'flag':
554
+ case 'all': return undefined;
555
+ }
556
+ };
557
+ const reads = new Set([...JSON.stringify(law.holds).matchAll(/"name":"([^"]+)"/g)].map(m => m[1]));
558
+ if (!ints.some(x => reads.has(x) || reads.has(`${x}$neg`)))
559
+ return undefined;
560
+ const body = p(law.holds);
561
+ return body === undefined ? undefined : quantified(law, body);
562
+ };
563
+ const argNames = (binders) => [...binders.matchAll(/\((\S+) :/g)].map(m => m[1]);
409
564
  const used = new Set(laws.flatMap(l => [...JSON.stringify(l.holds).matchAll(/"name":"([^"]+)"|"map":"([^"]+)"/g)].map(m => m[1] ?? m[2]).filter(name => name !== l.forall && !(l.keys ?? []).includes(name))));
410
565
  return `namespace ${id}
411
566
  ${[...vars.filter(v => used.has(v)).map(v => `abbrev ${q(v)} : Nat := ${vars.indexOf(v)}`), ...maps.filter(mp => used.has(mp)).map(mp => `abbrev ${q(mp)} : Nat := ${maps.indexOf(mp)}`)].join('\n')}
412
- ${laws.map((law, j) => (law.stated ? `-- ${law.name}: as written, with some key; proven (below) with the prover's witness in place\n` : '') + (JSON.stringify(law.holds).includes('"op":"if"') ? `-- ${law.name}: its terms read an if, so no plain statement here (${id}_laws holds it)` : `/-- ${law.name.replace(/-\//g, '- /')} -/
413
- theorem law${j} ${reach.binders} (h : ${reach.hyp}) : ${statement(law)} := by
414
- ${law.keys?.length ? `${reach.have ? reach.have(j) : `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
415
- exact fun k ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} => (evalC_iff (upd (${law.keys.reduce((acc, key, i) => `upd (${acc}) ${vars.indexOf(key)} k${i + 2}`, 'ρ')}) ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} k)` : `${reach.have ? reach.have(j) : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
416
- ${law.forall ? `exact fun k => (evalC_iff (upd ρ ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl k)` : `exact (evalC_iff ρ μ ${id}_l${j}.holds).mp hl`}`}`)).join('\n')}
567
+ ${laws.map((law, j) => {
568
+ if (law.ex)
569
+ return someOf(law, j);
570
+ if (JSON.stringify(law.holds).includes('"op":"if"'))
571
+ return `-- ${law.name}: its terms read an if, so no plain statement here (${id}_laws holds it)`;
572
+ const hyps = arithBinders(law), names = arithIn(law).map(hypName), rw = names.length ? `have hμ : ${statement(law, false)} := hl'\n rw [${names.join(', ')}] at hμ\n exact hμ` : `exact hl'`;
573
+ const own = law.keys?.length ? `${reach.have ? reach.have(j) : `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
574
+ have hl' := fun k ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} => (evalC_iff (upd (${law.keys.reduce((acc, key, i) => `upd (${acc}) ${vars.indexOf(key)} k${i + 2}`, 'ρ')}) ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} k)
575
+ ${rw}` : `${reach.have ? reach.have(j) : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
576
+ ${law.forall ? `have hl' := fun k => (evalC_iff (upd ρ ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl k)` : `have hl' := (evalC_iff ρ μ ${id}_l${j}.holds).mp hl`}
577
+ ${rw}`;
578
+ const int = intStatement(law);
579
+ return (law.stated ? `-- ${law.name}: as written, with some key; proven (below) with the prover's witness in place\n` : '') + `/-- ${law.name.replace(/-\//g, '- /')} -/
580
+ theorem law${j} ${reach.binders}${hyps} (h : ${reach.hyp}) : ${statement(law)} := by
581
+ ${own}` + (int === undefined ? '' : `
582
+ /-- ${law.name.replace(/-\//g, '- /')}, over Int -/
583
+ theorem law${j}_int ${reach.binders}${hyps} (h : ${reach.hyp}) : ${int} := by
584
+ have hn := law${j} ${[...argNames(reach.binders), ...names, 'h'].join(' ')}
585
+ ${law.forall ? `intro k${(law.keys ?? []).map((_, i) => ` k${i + 2}`).join('')}\n have hn := hn k${(law.keys ?? []).map((_, i) => ` k${i + 2}`).join('')}\n ` : ''}omega`);
586
+ }).join('\n')}
417
587
  end ${id}`;
418
588
  }
419
589
  // A liveness as Live.lean's record: from, to, the rank's parts, each helpful request with its region, the primes.
@@ -573,7 +743,9 @@ function machineNames(m, laws, allSteps, stepLaws, opts) {
573
743
  const primes = allSteps.length ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
574
744
  const vars = [...new Set([...m.params, ...m.inputs, ...Object.keys(m.state), ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flags.map(([f]) => `?${f}`),
575
745
  ...quantified.flatMap(qwits), ...boundKeys(m), ...(opts.tamper ? boundKeys(opts.tamper.machine) : []), ...stepLaws.flatMap(sl => (sl.forall ? [sl.forall] : [])), ...(opts.free ?? []),
576
- ...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(), ...(opts.record ?? []).map(f => `r.${f}`)])];
746
+ ...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(), ...(opts.record ?? []).map(f => `r.${f}`),
747
+ // (the naturals a law with "there exists" binds, which its statement sets: core/exists.ts)
748
+ ...laws.flatMap(l => l.ex?.vars ?? [])])];
577
749
  const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
578
750
  const maps = [...new Set([...baseMaps, ...carried, ...(allSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps])];
579
751
  return { vars, maps };
@@ -852,7 +1024,7 @@ def ${id}_proof : List SEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
852
1024
  theorem ${id}_check : checkSMachine ${id}_machine ${id}_proof = true := by ${checkBy}
853
1025
  theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_proof, e.Holds ρ M :=
854
1026
  smachine_sound ${id}_machine ${id}_proof ${id}_check
855
- ${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${opts.noFacts ? '' : factsProof(id, m, mapNames, vars, opts.simp)}${opts.tamper ? `
1027
+ ${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h`, lit: c, eLit: e })}${opts.noFacts ? '' : factsProof(id, m, mapNames, vars, opts.simp)}${opts.tamper ? `
856
1028
  -- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
857
1029
  def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
858
1030
  example : checkSMachine ${id}_tampered ${id}_proof = false := by ${checkBy}` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
@@ -1157,7 +1329,7 @@ ${cycProofText(id, p, [], vars, maps)}
1157
1329
  ${okLemmas}
1158
1330
  ${lawBlocks}
1159
1331
  ${lawsThm}
1160
- ${hm ? histBlock() : readable(id, laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '',
1332
+ ${hm ? histBlock() : readable(id, laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '', lit: c, eLit: e,
1161
1333
  have: j => `have hl : holdsOver ${id}_l${j} μ ${keys2(laws[j])} ρ := cyc_machine_holds (${id}_law${j}_holds ρ μ h)` })}${opts.noFacts ? '' : factsProof(id, m, maps, vars, opts.simp)}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}` };
1162
1334
  }
1163
1335
  // A fold from its cyclic proof (Bridge.lean's cyc_fold_sound): the fold numbered as foldToLean numbers it, each law's
@@ -1496,6 +1668,15 @@ function toLeanWith(items, opts) {
1496
1668
  for (let item of items) {
1497
1669
  const id = ident(item.id), tItem = performance.now();
1498
1670
  if ('machine' in item) {
1671
+ // Laws with "there exists" go in as the kernel proved them (core/exists.ts): the machine with the ghosts their
1672
+ // witnesses are, each law with its witness terms in place or over its ghosts, each stated again as ∃ (readable).
1673
+ if (item.laws.some(l => l.some?.length)) {
1674
+ const it = item, r = proveStepLaws(it.machine, it.laws, it.stepLaws ?? []);
1675
+ if (!r.proven || !r.laws)
1676
+ throw new Error(`${it.id}: not proven (${r.failed?.stuck ?? ''})`);
1677
+ const ghosts = [...(it.ghosts ?? []), ...Object.keys(r.machine?.state ?? {}).filter(n => !(n in it.machine.state))];
1678
+ item = { ...it, machine: r.machine ?? it.machine, laws: r.laws, ...(ghosts.length ? { ghosts } : {}) };
1679
+ }
1499
1680
  const m = item.machine;
1500
1681
  // Laws with some key go in as the prover proved them: with its witness in place (the stronger law).
1501
1682
  const someKey = (c) => JSON.stringify(c).includes('"op":"all"');
@@ -1517,7 +1698,7 @@ function toLeanWith(items, opts) {
1517
1698
  else if (cyc)
1518
1699
  certReport.usual.push({ id: item.id, why: cyc.why });
1519
1700
  parts.push(`-- ${item.id}\n${defs}${cyc && 'text' in cyc ? cyc.text : smachineToLean(id, m, item.laws, mopts)}`);
1520
- theorems.push(`${id}_laws`, ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
1701
+ theorems.push(`${id}_laws`, ...item.laws.flatMap((l, j) => (l.ex ? [`${id}.some${j}`] : [])), ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
1521
1702
  if (item.lean?.after)
1522
1703
  theorems.push(...[...item.lean.after(() => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1]));
1523
1704
  }
@@ -1574,7 +1755,7 @@ function toLeanWith(items, opts) {
1574
1755
  }
1575
1756
  // The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
1576
1757
  return `import History
1577
- ${cycTexts ? 'import BridgeRules\n' : ''}${items.some(item => 'cyclic' in item) ? (items.some(item => 'cyclic' in item && (item.bridge || item.recur || item.fold)) ? 'import Bridge\n' : 'import Cyclic\n') : ''}${items.some(item => 'cyclic' in item && item.data) ? 'import Data\n' : ''}import ${items.some(item => 'refinement' in item) ? 'Refine' : items.some(item => 'machine' in item && item.ghosts?.length) ? 'Ghost' : items.some(item => ('machine' in item && item.liveness?.length) || 'loop' in item) ? 'Live' : items.some(item => 'machine' in item) ? 'Sets' : 'Steps'}
1758
+ ${cycTexts ? 'import BridgeRules\n' : ''}${items.some(item => 'cyclic' in item) ? (items.some(item => 'cyclic' in item && (item.bridge || item.recur || item.fold)) ? 'import Bridge\n' : 'import Cyclic\n') : ''}${items.some(item => 'cyclic' in item && item.data) ? 'import Data\n' : ''}import ${items.some(item => 'refinement' in item) ? 'Refine' : items.some(item => 'machine' in item && (item.ghosts?.length || item.laws.some(l => l.some?.length))) ? 'Ghost' : items.some(item => ('machine' in item && item.liveness?.length) || 'loop' in item) ? 'Live' : items.some(item => 'machine' in item) ? 'Sets' : 'Steps'}
1578
1759
  open Kern Cert
1579
1760
  set_option maxRecDepth 100000
1580
1761
  set_option maxHeartbeats 0