gdp-wired 0.3.0 → 0.5.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,6 +1,6 @@
1
1
  import { atMost, n, not, pointWrite } from "../core/machine.js";
2
2
  import { add, k, scale, sub } from "../core/solver.js";
3
- import { canon, historyMachine, foldObligations, leafConstraints, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
3
+ import { canon, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
4
4
  import { grow, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
5
5
  import { refinement, refinementSides } from "../core/compose.js";
6
6
  import { leanTerms } from "./lean-terms.js";
@@ -52,11 +52,13 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
52
52
  // Columns: every variable of the hypotheses, then every cell any leaf uses (and the cells inside their keys),
53
53
  // shallower cells first so a key reads only earlier columns.
54
54
  const vars = globalVars ?? [...new Set(goal.hyps.flatMap(varsOf))];
55
- const cells = new Set();
55
+ const cells = new Set(), terms = new Set();
56
56
  const addCell = (name) => { if (cells.has(name))
57
57
  return; cells.add(name); for (const key of Object.keys(cellKey(name)))
58
58
  if (key.includes('['))
59
- addCell(key); };
59
+ addCell(key);
60
+ else if (key.startsWith('⟦'))
61
+ terms.add(key); };
60
62
  // Monomials: every one a leaf's rows or products use, or that a product term has under the leaf's path.
61
63
  const monos = new Set();
62
64
  const subterms = (c) => {
@@ -77,11 +79,29 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
77
79
  wc(c);
78
80
  return out2;
79
81
  };
82
+ // Every choice in the hypotheses has its column, whether or not a leaf's rows keep it (a column can cancel, T − T,
83
+ // yet Lean's linear form of T still needs it): each distinct subterm once, as a run's states are shared.
84
+ const seenTerms = new WeakSet();
85
+ const choices = (t) => {
86
+ if (seenTerms.has(t))
87
+ return;
88
+ seenTerms.add(t);
89
+ if (t.op === 'sub' || t.op === 'min' || t.op === 'max' || t.op === 'if')
90
+ terms.add(termColumn(t));
91
+ for (const val of Object.values(t))
92
+ if (Array.isArray(val))
93
+ val.forEach(x => choices(x));
94
+ else if (typeof val === 'object' && val !== null)
95
+ choices(val);
96
+ };
97
+ goal.hyps.forEach(choices);
80
98
  leaves(tree, new Map(), (leaf, path) => {
81
99
  for (const r of withProducts(leafConstraints(goal, path), leaf.products ?? []))
82
100
  for (const key of Object.keys(r.lin)) {
83
101
  if (key.includes('['))
84
102
  addCell(key);
103
+ else if (key.startsWith('⟦'))
104
+ terms.add(key);
85
105
  else if (key.includes('*'))
86
106
  monos.add(key);
87
107
  }
@@ -93,7 +113,8 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
93
113
  monos.add(key);
94
114
  });
95
115
  const depth = (name) => name.split('[').length;
96
- const cols = [...vars, ...[...monos].sort((a, b) => a.split('*').length - b.split('*').length), ...[...cells].sort((a, b) => depth(a) - depth(b))];
116
+ // Term columns (open choices, each standing for its term's value) before the cells, whose keys may read them.
117
+ const cols = [...vars, ...[...monos].sort((a, b) => a.split('*').length - b.split('*').length), ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...[...cells].sort((a, b) => depth(a) - depth(b))];
97
118
  const col = new Map(cols.map((name, j) => [name, j]));
98
119
  const maps = globalMaps ?? [...new Set([...[...cells].map(cellMap), ...goal.hyps.flatMap(h => [...JSON.stringify(h).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))])];
99
120
  const dense = (lin) => {
@@ -110,11 +131,12 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
110
131
  return coefs;
111
132
  };
112
133
  const row = (lin) => `⟨${list(dense(lin).map(int))}, ${int(lin['1'] ?? 0)}⟩`;
113
- const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
114
- : name.includes('*') ? `.mono ${list(name.split('*').map(f => { const j = vars.indexOf(f); if (j < 0)
115
- throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
116
- : `.var ${vars.indexOf(name)}`));
117
134
  const { e, c } = leanTerms(vars, maps, ex.name);
135
+ const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
136
+ : name.startsWith('⟦') ? `.term (${e(columnTerm(name))})`
137
+ : name.includes('*') ? `.mono ${list(name.split('*').map(f => { const j = vars.indexOf(f); if (j < 0)
138
+ throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
139
+ : `.var ${vars.indexOf(name)}`));
118
140
  let leafNo = 0;
119
141
  const lean = (t, path) => {
120
142
  if (!('leaf' in t)) {
@@ -363,13 +385,16 @@ function liveLit(l, t, ix, primes, primeMaps) {
363
385
  ${primes},
364
386
  ${primeMaps}⟩`;
365
387
  }
388
+ // The base twin's export (Progress.lean, StepLaw.lean, Steps.lean): laws, step laws, k-step laws and progress over a
389
+ // machine with maps and flags, for the demos in export-kernel.ts. Everything built on top (liveness, ghosts, refinement)
390
+ // goes through the sets twin, smachineToLean.
366
391
  export function mmachineToLean(id, m, laws, opts = {}) {
367
392
  const r = proveLaws(m, laws);
368
393
  if (!r.proven || !r.proof)
369
394
  throw new Error(`${id}: not proven`);
370
395
  const keysOf = [...new Set(laws.flatMap(l => (l.forall ? [l.forall] : [])))];
371
396
  const flagNames = Object.keys(m.step.flags ?? {});
372
- const primed = !!(opts.progress || opts.stepLaws?.length || opts.liveness?.length);
397
+ const primed = !!(opts.progress || opts.stepLaws?.length);
373
398
  const primes = primed ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
374
399
  // A k-step law reads the j-th request's inputs (j >= 2) by input@j.
375
400
  const later = [...new Set((opts.stepLaws ?? []).flatMap(sl => Array.from({ length: (sl.steps ?? 1) - 1 }, (_, j) => m.inputs.map(i => `${i}@${j + 2}`)).flat()))];
@@ -421,9 +446,6 @@ example : checkProgress ${id}_machine ${id}_proof ${pc.id}_tampered = false := b
421
446
  if (opts.stepLaws?.length && (!sp?.proven || !sp.proof))
422
447
  throw new Error(`${id}: step laws not proven`);
423
448
  const inlineFlags = (c) => substC(c, { vars: {}, maps: {}, flags: m.step.flags ?? {} });
424
- // Lean's inlineC: each flag a condition reads becomes `if c then 1 else 0 = 1`, c as the machine's literal has it.
425
- const flagVar = Object.fromEntries(Object.keys(m.step.flags ?? {}).map(f => [f, { op: '=', a: { op: 'var', name: `?${f}` }, b: n(1) }]));
426
- const leanInline = (c) => substC(c, { vars: {}, maps: {}, flags: Object.fromEntries(Object.entries(m.step.flags ?? {}).map(([f, fc]) => [f, { op: '=', a: { op: 'if', c: substC(fc, { vars: {}, maps: {}, flags: flagVar }), then: n(1), else: n(0) }, b: n(1) }])) });
427
449
  const stepRecords = (opts.stepLaws ?? []).map((sl, j) => {
428
450
  const goal = stepLawObligations(m, laws, sp.proof.instances, sl)[0];
429
451
  const pr = enc(goal, sp.proof.trees[goal.name]);
@@ -454,86 +476,7 @@ def ${id}_step${j} : KStep := ⟨${list(Array.from({ length: k }, (_, s2) => (wh
454
476
  theorem ${id}_step${j}_holds : KStepHolds ${id}_machine ${id}_step${j} :=
455
477
  kstep_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
456
478
  });
457
- // Liveness through Kern.leads_sound (Live.lean): Lean builds its step laws (stay, cover, one per helpful request) from
458
- // the liveness; the trees and instances are the kernel's.
459
- const liveRecords = (opts.liveness ?? []).map((l, j) => {
460
- const { stay, cover, helps } = livenessParts(m, l);
461
- const a = proveStepLaws(m, laws, [stay, cover, ...helps]);
462
- if (!a.proven || !a.proof)
463
- throw new Error(`${id}: liveness ${l.name} not proven`);
464
- const proofOf = (sl) => {
465
- const inst = a.proof.perStep?.[sl.name] ?? a.proof.instances, g = stepLawObligations(m, laws, inst, sl)[0], pr = enc(g, a.proof.trees[g.name]);
466
- return { insts: list(laws.flatMap((lw, li) => (lw.forall ? (inst[lw.name] ?? []).map(key => `(${li}, ${any0.e(key)})`) : []))), tree: `(${pr.tab}, ${pr.tree})` };
467
- };
468
- const ps = proofOf(stay), pc = proofOf(cover), ph = helps.map(proofOf), lid = `${id}_live${j}`;
469
- // Weak fairness, where each helpful request is proven allowed in its region, its other inputs free (Live.lean's
470
- // fAllowGoal, built here as Lean builds it: the flags in the assumption inlined as `if c then 1 else 0 = 1`).
471
- const allows = helps.map((hs, i) => {
472
- const found = withWitness(m, livenessHelpful(l)[i].request, wit => {
473
- const r2 = proveBuilt(m, laws, inst => ({ name: `${hs.name}: allowed`, hyps: [m.pre ?? trueC, ...laws.filter(lw => !lw.forall).map(lw => lw.holds),
474
- ...laws.flatMap(lw => (lw.forall ? (inst[lw.name] ?? []).map(key => substC(lw.holds, { vars: { [lw.forall]: key }, maps: {}, flags: {} })) : [])),
475
- { op: 'and', xs: [hs.when, reqTS(wit)] }, { op: 'not', x: leanInline(m.assume ?? trueC) }] }));
476
- return r2.proven ? r2 : undefined;
477
- });
478
- if (!found)
479
- return undefined;
480
- const r2 = found.out, pr = enc(r2.goal, r2.tree);
481
- return `(${list(found.wit.map(([inp, t]) => `(${ix(inp)}, ${any0.e(t)})`))}, ${list(laws.flatMap((lw, li) => (lw.forall ? (r2.instances[lw.name] ?? []).map(key => `(${li}, ${any0.e(key)})`) : [])))}, (${pr.tab}, ${pr.tree}))`;
482
- });
483
- const wf = allows.every(a => a !== undefined) ? `
484
- theorem ${lid}_allows : liveAllowsOk ${id}_machine ${id}_proof ${lid} ${list(allows)} = true := by decide +kernel
485
- /-- ${l.name}, under weak fairness: along every run that takes each helpful request whenever, from some point on, it
486
- stays allowed, \`from\` is followed by \`to\`. -/
487
- theorem ${lid}_holds_wf (run : FRun ${id}_machine)
488
- (fair : ∀ h ∈ ${lid}.helps, ∀ n, (∀ m, n ≤ m → ∃ ι : Nat → Nat, (∀ p ∈ h.2, ι p.1 = evalE (run.st m).1 (run.st m).2 p.2)
489
- ∧ evalC (decideFlags ${id}_machine.flags (withInputs ${id}_machine.m.inputs (run.st m).1 ι) (run.st m).2) (run.st m).2 ${id}_machine.m.assume = true) →
490
- ∃ m, n ≤ m ∧ ∀ p ∈ h.2, run.req m p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
491
- ∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
492
- leads_wf_sound ${id}_machine ${id}_proof ${lid} _ _ _ _ _ _ ${id}_check ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps ${lid}_allows run fair` : '';
493
- return `-- Liveness: ${l.name}
494
- def ${lid} : Live := ${liveLit(l, any0, ix, list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), list(baseMaps.map(mp => `(${mapNames.indexOf(mp)}, ${mapNames.indexOf(`${mp}'`)})`)))}
495
- theorem ${lid}_ok : liveOk ${id}_machine ${lid} = true := by decide +kernel
496
- theorem ${lid}_stay : checkStepLaw ${id}_machine ${id}_proof (liveStay ${lid} ${ps.insts} ${ps.tree}) = true := by decide +kernel
497
- theorem ${lid}_cover : checkStepLaw ${id}_machine ${id}_proof (liveCover ${lid} ${pc.insts} ${pc.tree}) = true := by decide +kernel
498
- theorem ${lid}_helps : liveHelpsOk ${id}_machine ${id}_proof ${lid} ${list(ph.map(p => `(${p.insts}, ${p.tree})`))} = true := by decide +kernel
499
- /-- ${l.name}: along every run that takes each helpful request infinitely often, \`from\` is followed by \`to\`. -/
500
- theorem ${lid}_holds (run : FRun ${id}_machine)
501
- (fair : ∀ h ∈ ${lid}.helps, ∀ n, ∃ m, n ≤ m ∧ ∀ p ∈ h.2, run.req m p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
502
- ∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
503
- leads_sound ${id}_machine ${id}_proof ${lid} _ _ _ _ _ ${id}_check ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps run fair${wf}`;
504
- });
505
- // A ghosted machine (compose.ts's ghost): the original and its ghosts, the machine Lean checks built from them
506
- // (Ghost.lean), so the laws that read no ghost are laws of the original.
507
- const gv = opts.ghosts ?? [];
508
- const orig = { ...m, state: Object.fromEntries(Object.entries(m.state).filter(([n2]) => !gv.includes(n2))), step: { ...m.step, state: Object.fromEntries(Object.entries(m.step.state ?? {}).filter(([n2]) => !gv.includes(n2))) } };
509
- const rf = opts.refined;
510
- const machineDef = rf ? `def ${id}_refined : Refined := ⟨${lit(rf.impl)},
511
- ${lit(rf.spec)},
512
- ${list(Object.entries(rf.wit).map(([inp, e2]) => `(${ix(`b_${inp}`)}, ${any0.e(e2)})`))},
513
- ${any0.c(rf.stutter)}⟩
514
- def ${id}_machine : FMMachine := ${id}_refined.full` : gv.length ? `def ${id}_ghosted : Ghosted := ⟨${lit(orig)},
515
- ${list(gv.map(v2 => `(${ix(v2)}, ${any0.e(m.state[v2])}, ${any0.e(m.step.state?.[v2] ?? { op: 'var', name: v2 })})`))}⟩
516
- def ${id}_machine : FMMachine := ${id}_ghosted.full
517
- theorem ${id}_ghostOk : ghostOk ${id}_ghosted = true := by decide +kernel` : `def ${id}_machine : FMMachine := ${lit(m)}`;
518
- // What the refinement means (Refine.lean's refines), from the laws and the step law (the spec allows the request).
519
- const refinesText = () => {
520
- const sl = opts.stepLaws[0], goal = stepLawObligations(m, laws, sp.proof.instances, sl)[0], pr = enc(goal, sp.proof.trees[goal.name]);
521
- const insts = list(laws.flatMap((l, li) => (l.forall ? (sp.proof.instances[l.name] ?? []).map(key => `(${li}, ${any0.e(key)})`) : [])));
522
- const primes = list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), primeMaps = list(baseMaps.map(mp => `(${mapNames.indexOf(mp)}, ${mapNames.indexOf(`${mp}'`)})`));
523
- return `
524
-
525
- -- What the refinement means: every reachable state of the implementation, beside a reachable state of the spec, related
526
- -- by every law.
527
- theorem ${id}_allowed : checkStepLaw ${id}_machine ${id}_proof ⟨trueC, ${id}_refined.allowed, ${primes}, ${primeMaps}, ${insts}, (${pr.tab}, ${pr.tree})⟩ = true := by decide +kernel
528
- theorem ${id}_refines : ∀ ρ M, FMReach ${id}_refined.impl ρ M → ∃ ρc Mc σ Ms, FMReach ${id}_refined.full ρc Mc ∧ Agree ${id}_refined.SV ρc ρ
529
- ∧ (∀ mp, mp ∉ ${id}_refined.SM → Mc mp = M mp) ∧ FMReach ${id}_refined.spec σ Ms ∧ Agree ${id}_refined.X σ ρc
530
- ∧ (∀ mp, mp ∉ ${id}_refined.IM → Ms mp = Mc mp) ∧ ∀ e ∈ ${id}_proof, e.law.Holds ρc Mc :=
531
- refines ${id}_refined (by decide +kernel) (by decide +kernel) ${id}_proof ${id}_check ${rf.specPre} ⟨rfl, rfl⟩ ${primes} ${primeMaps} ${insts} (${pr.tab}, ${pr.tree}) ${id}_allowed (by decide +kernel)`;
532
- };
533
- const ghostLaws = gv.length ? `
534
- -- The laws that read no ghost hold of the machine without its ghosts.
535
- theorem ${id}_orig_laws : ∀ ρ M, FMReach ${id}_ghosted.orig ρ M → ∀ e ∈ ${id}_proof, avoids ${id}_ghosted.vars e.law.holds = true → e.law.Holds ρ M :=
536
- ghost_laws ${id}_ghosted ${id}_ghostOk ${id}_proof ${id}_check` : '';
479
+ const machineDef = `def ${id}_machine : FMMachine := ${lit(m)}`;
537
480
  return `-- ${m.name}: ${laws.map(l => l.name).join('; ')}${flagNames.length ? `, with flags ${flagNames.join(', ')}` : ''} (variables ${vars.map((v2, j) => `${j} ${v2}`).join(', ')}; maps ${mapNames.map((v2, j) => `${j} ${v2}`).join(', ')})
538
481
  ${machineDef}
539
482
  ${entries.join('\n')}
@@ -544,7 +487,7 @@ theorem ${id}_laws : ∀ ρ M, FMReach ${id}_machine ρ M → ∀ e ∈ ${id}_pr
544
487
  ${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `FMReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${opts.corollary ? `\n${opts.corollary}` : ''}${opts.tamper ? `
545
488
  -- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
546
489
  def ${id}_tampered : FMMachine := ${lit(opts.tamper.machine)}
547
- example : checkFMMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${progress.length ? `\n\n${progress.join('\n\n')}` : ''}${stepRecords.length ? `\n\n${stepRecords.join('\n\n')}` : ''}${liveRecords.length ? `\n\n${liveRecords.join('\n\n')}` : ''}${ghostLaws}${rf ? refinesText() : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
490
+ example : checkFMMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${progress.length ? `\n\n${progress.join('\n\n')}` : ''}${stepRecords.length ? `\n\n${stepRecords.join('\n\n')}` : ''}`;
548
491
  }
549
492
  // Machines with sets through Kern.smachine_sound and Kern.skstep_sound (Sets.lean): maps a request carries, flags that
550
493
  // hold at every key (each its 0/1 variable ?f, closed at the proof's keys and at a witness w?f) and writes at every
@@ -561,22 +504,98 @@ export const hasSets = (m) => !!m.inputMaps?.length || Object.values(m.step.flag
561
504
  || !!Object.keys(m.fns ?? {}).length || !!m.facts?.length;
562
505
  const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ? [c.key] : [])), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : []))];
563
506
  export function smachineToLean(id, m, laws, opts = {}) {
564
- const stepLaws = opts.stepLaws ?? [];
565
- // Liveness through Kern.sleads_sound (Live.lean): its step laws proven with the others, Lean building each from it.
566
- const liveParts = (opts.liveness ?? []).map(l => livenessParts(m, l)), liveSteps = liveParts.flatMap(p => [p.stay, p.cover, ...p.helps]);
567
- const r = proveStepLaws(m, laws, [...stepLaws, ...liveSteps]);
507
+ const stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
508
+ const claims = [];
509
+ const claimSteps = () => claims.flatMap(cl => cl.steps);
510
+ // The machine's own step laws, one step or k (Sets.lean's skstep_sound).
511
+ if (!rf)
512
+ stepLaws.forEach((sl, j) => claims.push({ steps: [sl], allows: [], render: h => {
513
+ const { pr, inst } = h.step(sl), kk = sl.steps ?? 1;
514
+ const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
515
+ const ren = Array.from({ length: kk }, (_, s2) => (s2 === 0 ? '[]' : list(ext.map(v => `(${ix(v)}, ${ix(`${v}@${s2 + 1}`)})`))));
516
+ const mren = Array.from({ length: kk }, (_, s2) => (s2 === 0 ? '[]' : list(carried.map(mp => `(${mx(mp)}, ${mx(`${mp}@${s2 + 1}`)})`))));
517
+ return `-- Step law${kk > 1 ? ` over ${kk} steps` : ''}: ${sl.name}
518
+ def ${id}_step${j} : SKStep := ⟨⟨${list(Array.from({ length: kk }, (_, s2) => (whens[s2] ? c(inline(whens[s2])) : TRUE)))},
519
+ ${c(inline(sl.then))},
520
+ ${list(ren)},
521
+ ${list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`))},
522
+ ${list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`))},
523
+ ${list(insts(inst))},
524
+ (${pr.tab}, ${pr.tree})⟩,
525
+ ${list(mren)},
526
+ ${list(qinsts(inst))},
527
+ ${list(finsts(inst))},
528
+ ${list(pinsts(inst))},
529
+ [],
530
+ ${list(Array.from({ length: kk - 1 }, (_, s2) => list(Object.keys(m.state).map(n2 => `(${ix(n2)}, ${ix(stateName(n2, s2 + 1))})`))))}⟩
531
+ theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
532
+ skstep_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
533
+ } }));
534
+ // A refinement (Refine.lean's SRefined): its step law, that the spec stutters or allows the request it is sent; srefines
535
+ // says what the whole means.
536
+ if (rf)
537
+ claims.push({ steps: [stepLaws[0]], allows: [], render: h => {
538
+ const primesL = list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), primeMapsL = list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`));
539
+ const proofL = h.sproof(stepLaws[0]);
540
+ return `theorem ${id}_allowed : checkSKStep ${id}_machine ${id}_proof (srStep ${id}_refined ${primesL} ${primeMapsL} ${proofL}) = true := by decide +kernel
541
+ theorem ${id}_refines : ∀ ρ M, SReach ${id}_refined.impl ρ M → ∃ ρc Mc σ Ms, SReach ${id}_refined.full ρc Mc ∧ Agree ${id}_refined.SV ρc ρ
542
+ ∧ (∀ mp, mp ∉ ${id}_refined.SM → Mc mp = M mp) ∧ SReach ${id}_refined.spec σ Ms ∧ Agree ${id}_refined.X σ ρc
543
+ ∧ (∀ mp, mp ∉ ${id}_refined.IM → mp ∉ ${id}_refined.spec.carried → Ms mp = Mc mp) ∧ ∀ e ∈ ${id}_proof, e.Holds ρc Mc :=
544
+ srefines ${id}_refined (by decide +kernel) (by decide +kernel) ${id}_proof ${id}_check ${rf.specPre} ⟨rfl, rfl⟩ ${primesL} ${primeMapsL} ${proofL} ${id}_allowed (by decide +kernel) (by decide +kernel) (by decide +kernel)`;
545
+ } });
546
+ // Liveness (Live.lean's sleads_sound): its step laws (stay, cover, one per helpful request), and where each helpful
547
+ // request is proven allowed in its region, the same under weak fairness (sleads_wf_sound).
548
+ for (const [j, l] of (opts.liveness ?? []).entries()) {
549
+ const { stay, cover, helps } = livenessParts(m, l), helpful = livenessHelpful(l), lid = `${id}_live${j}`;
550
+ const allows = helps.map((hs, i) => ({ when: hs.when, request: helpful[i].request }));
551
+ claims.push({ steps: [stay, cover, ...helps], allows, render: h => {
552
+ const al = allows.map(h.allow);
553
+ const wf = al.every(a => a !== undefined) ? `
554
+ def ${lid}_ap : List (List (Nat × E) × SProof × List E) := ${list(al)}
555
+ theorem ${lid}_allows : sliveAllowsOk ${id}_machine ${id}_proof ${lid} ${lid}_ap = true := by decide +kernel
556
+ /-- ${l.name}, under weak fairness: along every run that takes each helpful request whenever, from some point on, it
557
+ stays allowed, \`from\` is followed by \`to\`. -/
558
+ theorem ${lid}_holds_wf (run : SRun ${id}_machine)
559
+ (fair : ∀ h ∈ ${lid}.helps, ∀ n, (∀ m, n ≤ m → ∃ ι κ, (∀ p ∈ h.2, ι p.1 = evalE (run.st m).1 (run.st m).2 p.2)
560
+ ∧ evalC (sView ${id}_machine (run.st m).1 (run.st m).2 ι κ).1 (sView ${id}_machine (run.st m).1 (run.st m).2 ι κ).2 ${id}_machine.f.m.assume = true) →
561
+ ∃ m, n ≤ m ∧ ∀ p ∈ h.2, (run.req m).1 p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
562
+ ∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
563
+ sleads_wf_sound ${id}_machine ${id}_proof ${lid} _ _ _ _ ${id}_check ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps ${lid}_allows run fair` : '';
564
+ return `-- Liveness: ${l.name}
565
+ def ${lid} : Live := ${liveLit(l, { e, c }, ix, list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`)))}
566
+ def ${lid}_sp : SProof := ${h.sproof(stay)}
567
+ def ${lid}_cp : SProof := ${h.sproof(cover)}
568
+ def ${lid}_hp : List SProof := ${list(helps.map(h.sproof))}
569
+ theorem ${lid}_ok : sliveOk ${id}_machine ${lid} = true := by decide +kernel
570
+ theorem ${lid}_stay : checkSKStep ${id}_machine ${id}_proof (sliveStep ${lid} ${lid}.pend (liveStayC ${lid}) ${lid}_sp) = true := by decide +kernel
571
+ theorem ${lid}_cover : checkSKStep ${id}_machine ${id}_proof (sliveStep ${lid} ${lid}.pend (liveCoverC ${lid}) ${lid}_cp) = true := by decide +kernel
572
+ theorem ${lid}_helps : sliveHelpsOk ${id}_machine ${id}_proof ${lid} ${lid}_hp = true := by decide +kernel
573
+ /-- ${l.name}: along every run that takes each helpful request infinitely often, \`from\` is followed by \`to\`. -/
574
+ theorem ${lid}_holds (run : SRun ${id}_machine)
575
+ (fair : ∀ h ∈ ${lid}.helps, ∀ n, ∃ m, n ≤ m ∧ ∀ p ∈ h.2, (run.req m).1 p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
576
+ ∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
577
+ sleads_sound ${id}_machine ${id}_proof ${lid} _ _ _ ${id}_check ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps run fair${wf}`;
578
+ } });
579
+ }
580
+ // Ghosts (Ghost.lean's sghost_laws): the laws that read no ghost hold of the machine without its ghosts.
581
+ if (gv.length)
582
+ claims.push({ steps: [], allows: [], render: () => `-- The laws that read no ghost hold of the machine without its ghosts.
583
+ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈ ${id}_proof, avoids ${id}_ghosted.vars e.law.holds = true → e.Holds ρ M :=
584
+ sghost_laws ${id}_ghosted ${id}_ghostOk ${id}_proof ${id}_check` });
585
+ const allSteps = claimSteps();
586
+ const r = proveStepLaws(m, laws, allSteps);
568
587
  if (!r.proven || !r.proof)
569
588
  throw new Error(`${id}: not proven`);
570
589
  const lp = r.proof.laws, si = r.proof.instances;
571
590
  const flags = Object.entries(m.step.flags ?? {}), quantified = flags.filter(([, c]) => c.op === 'all').map(([f]) => f);
572
591
  const carried = m.inputMaps ?? [], facts = m.facts ?? [], keysOf = [...new Set([...laws, ...facts].flatMap(l => [l.forall, ...(l.keys ?? [])].filter((k) => !!k)))];
573
- const k = Math.max(1, ...stepLaws.map(sl => sl.steps ?? 1));
592
+ const k = Math.max(1, ...allSteps.map(sl => sl.steps ?? 1));
574
593
  const ext = [...m.inputs, ...quantified.flatMap(f => [`?${f}`, `w?${f}`])];
575
594
  const later = Array.from({ length: k - 1 }, (_, j) => ext.map(v => `${v}@${j + 2}`)).flat();
576
595
  const laterMaps = Array.from({ length: k - 1 }, (_, j) => carried.map(mp => `${mp}@${j + 2}`)).flat();
577
- const primes = stepLaws.length || liveSteps.length ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
596
+ const primes = allSteps.length ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
578
597
  const instOf = (sl) => r.proof.perStep?.[sl.name] ?? si;
579
- const goalsL = obligations(m, laws, lp.instances), goalsS = [...stepLaws, ...liveSteps].map(sl => stepLawObligations(m, laws, instOf(sl), sl)[0]);
598
+ const goalsL = obligations(m, laws, lp.instances), goalsS = allSteps.map(sl => stepLawObligations(m, laws, instOf(sl), sl)[0]);
580
599
  // Weak fairness over sets: each helpful request allowed in its region carrying empty sets (Live.lean's sAllowGoal,
581
600
  // built here as Lean builds it: facts, laws, `when`, each quantified flag closed, the carried maps 0 at the keys read,
582
601
  // and the assumption's failing, its plain flags inlined as `if c then 1 else 0 = 1`).
@@ -622,16 +641,17 @@ export function smachineToLean(id, m, laws, opts = {}) {
622
641
  const last = { op: 'not', x: sInline(m.assume ?? trueC) }, zkeys = carriedKeys([...base, last]);
623
642
  return { name: 'allowed', hyps: [...base, ...(m.inputMaps ?? []).flatMap(mp => zkeys.map(t => eqC({ op: 'get', map: mp, key: t }, zero1(0)))), last], zkeys };
624
643
  };
625
- const allowS = liveParts.map((p2, j) => p2.helps.map((hs, i) => withWitness(m, livenessHelpful(opts.liveness[j])[i].request, wit => {
626
- const r2 = proveBuilt(m, laws, inst => allowGoalS(inst, { op: 'and', xs: [hs.when, reqTS(wit)] }));
627
- return r2.proven ? r2 : undefined;
628
- })));
629
- const allowGoals = allowS.flat().flatMap(a => (a ? [a.out.goal] : []));
644
+ const allowed = new Map(claims.flatMap(cl => cl.allows).map(a => [a, withWitness(m, a.request, wit => {
645
+ const r2 = proveBuilt(m, laws, inst => allowGoalS(inst, { op: 'and', xs: [a.when, reqTS(wit)] }));
646
+ return r2.proven ? r2 : undefined;
647
+ })]));
648
+ const allowGoals = [...allowed.values()].flatMap(a => (a ? [a.out.goal] : []));
630
649
  const vars = [...new Set([...m.params, ...m.inputs, ...Object.keys(m.state), ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flags.map(([f]) => `?${f}`),
631
650
  ...quantified.map(f => `w?${f}`), ...boundKeys(m), ...(opts.tamper ? boundKeys(opts.tamper.machine) : []), ...stepLaws.flatMap(sl => (sl.forall ? [sl.forall] : [])), ...(opts.free ?? []),
632
- ...primes, ...later, ...[...goalsL, ...goalsS, ...allowGoals].flatMap(g => g.hyps.flatMap(varsOf))])];
651
+ ...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(),
652
+ ...[...goalsL, ...goalsS, ...allowGoals].flatMap(g => g.hyps.flatMap(varsOf))])];
633
653
  const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
634
- const mapNames = [...new Set([...baseMaps, ...carried, ...(stepLaws.length || liveSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps,
654
+ const mapNames = [...new Set([...baseMaps, ...carried, ...(allSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps,
635
655
  ...[...JSON.stringify([...goalsL, ...goalsS, ...allowGoals]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x2 => x2[1])])];
636
656
  const enc = (g, tree) => encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, mapNames);
637
657
  const { e, c } = leanTerms(vars, mapNames, id);
@@ -669,85 +689,34 @@ export function smachineToLean(id, m, laws, opts = {}) {
669
689
  const g0 = goalsL[2 * j], g1 = goalsL[2 * j + 1], a = enc(g0, lp.trees[g0.name]), b = enc(g1, lp.trees[g1.name]), inst = lp.instances[law.name] ?? {};
670
690
  return `def ${id}_l${j} : MLaw := ⟨${c(law.holds)}, ${law.forall ? `some ${ix(law.forall)}` : 'none'}⟩
671
691
  def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` : '#')}, ${list((law.keys ?? []).map(key => String(ix(key))))}, ${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))},
672
- (${a.tab}, ${a.tree}),
673
- (${b.tab}, ${b.tree})⟩`;
674
- });
675
- // A refinement over sets (Refine.lean's SRefined): Lean builds the composite from its two sides; its one step law is
676
- // that the spec stutters or allows the request it is sent, and srefines says what the whole means.
677
- const rf = opts.refined;
678
- const refineRecord = rf ? (() => {
679
- const g = goalsS[0], pr = enc(g, r.proof.trees[g.name]), inst = instOf(stepLaws[0]);
680
- const primesL = list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), primeMapsL = list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`));
681
- const proofL = `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, ${pr.tree}))`;
682
- return `theorem ${id}_allowed : checkSKStep ${id}_machine ${id}_proof (srStep ${id}_refined ${primesL} ${primeMapsL} ${proofL}) = true := by decide +kernel
683
- theorem ${id}_refines : ∀ ρ M, SReach ${id}_refined.impl ρ M → ∃ ρc Mc σ Ms, SReach ${id}_refined.full ρc Mc ∧ Agree ${id}_refined.SV ρc ρ
684
- ∧ (∀ mp, mp ∉ ${id}_refined.SM → Mc mp = M mp) ∧ SReach ${id}_refined.spec σ Ms ∧ Agree ${id}_refined.X σ ρc
685
- ∧ (∀ mp, mp ∉ ${id}_refined.IM → mp ∉ ${id}_refined.spec.carried → Ms mp = Mc mp) ∧ ∀ e ∈ ${id}_proof, e.Holds ρc Mc :=
686
- srefines ${id}_refined (by decide +kernel) (by decide +kernel) ${id}_proof ${id}_check ${rf.specPre} ⟨rfl, rfl⟩ ${primesL} ${primeMapsL} ${proofL} ${id}_allowed (by decide +kernel) (by decide +kernel) (by decide +kernel)`;
687
- })() : '';
688
- const stepRecords = (rf ? [] : stepLaws).map((sl, j) => {
689
- const g = goalsS[j], pr = enc(g, r.proof.trees[g.name]), kk = sl.steps ?? 1;
690
- const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
691
- const ren = Array.from({ length: kk }, (_, s2) => (s2 === 0 ? '[]' : list(ext.map(v => `(${ix(v)}, ${ix(`${v}@${s2 + 1}`)})`))));
692
- const mren = Array.from({ length: kk }, (_, s2) => (s2 === 0 ? '[]' : list(carried.map(mp => `(${mx(mp)}, ${mx(`${mp}@${s2 + 1}`)})`))));
693
- return `-- Step law${kk > 1 ? ` over ${kk} steps` : ''}: ${sl.name}
694
- def ${id}_step${j} : SKStep := ⟨⟨${list(Array.from({ length: kk }, (_, s2) => (whens[s2] ? c(inline(whens[s2])) : TRUE)))},
695
- ${c(inline(sl.then))},
696
- ${list(ren)},
697
- ${list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`))},
698
- ${list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`))},
699
- ${list(insts(instOf(sl)))},
700
- (${pr.tab}, ${pr.tree})⟩,
701
- ${list(mren)},
702
- ${list(qinsts(instOf(sl)))},
703
- ${list(finsts(instOf(sl)))},
704
- ${list(pinsts(instOf(sl)))}⟩
705
- theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
706
- skstep_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
707
- });
708
- const liveRecords = (opts.liveness ?? []).map((l, j) => {
709
- const { stay, cover, helps } = liveParts[j], lid = `${id}_live${j}`;
710
- const al = allowS[j];
711
- const wf = al.every(a => a !== undefined) ? `
712
- def ${lid}_ap : List (List (Nat × E) × SProof × List E) := ${list(al.map(f2 => {
713
- const a = f2.out, pr = enc(a.goal, a.tree), inst = a.instances;
714
- return `(${list(f2.wit.map(([inp, t]) => `(${ix(inp)}, ${e(t)})`))}, (${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, ${pr.tree})), ${list(a.goal.zkeys.map(e))})`;
715
- }))}
716
- theorem ${lid}_allows : sliveAllowsOk ${id}_machine ${id}_proof ${lid} ${lid}_ap = true := by decide +kernel
717
- /-- ${l.name}, under weak fairness: along every run that takes each helpful request whenever, from some point on, it
718
- stays allowed, \`from\` is followed by \`to\`. -/
719
- theorem ${lid}_holds_wf (run : SRun ${id}_machine)
720
- (fair : ∀ h ∈ ${lid}.helps, ∀ n, (∀ m, n ≤ m → ∃ ι κ, (∀ p ∈ h.2, ι p.1 = evalE (run.st m).1 (run.st m).2 p.2)
721
- ∧ evalC (sView ${id}_machine (run.st m).1 (run.st m).2 ι κ).1 (sView ${id}_machine (run.st m).1 (run.st m).2 ι κ).2 ${id}_machine.f.m.assume = true) →
722
- ∃ m, n ≤ m ∧ ∀ p ∈ h.2, (run.req m).1 p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
723
- ∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
724
- sleads_wf_sound ${id}_machine ${id}_proof ${lid} _ _ _ _ ${id}_check ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps ${lid}_allows run fair` : '';
725
- const proofOf = (sl) => {
726
- const g = goalsS[stepLaws.length + liveSteps.indexOf(sl)], pr = enc(g, r.proof.trees[g.name]), inst = instOf(sl);
727
- return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, ${pr.tree}))`;
728
- };
729
- return `-- Liveness: ${l.name}
730
- def ${lid} : Live := ${liveLit(l, { e, c }, ix, list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`)))}
731
- def ${lid}_sp : SProof := ${proofOf(stay)}
732
- def ${lid}_cp : SProof := ${proofOf(cover)}
733
- def ${lid}_hp : List SProof := ${list(helps.map(proofOf))}
734
- theorem ${lid}_ok : sliveOk ${id}_machine ${lid} = true := by decide +kernel
735
- theorem ${lid}_stay : checkSKStep ${id}_machine ${id}_proof (sliveStep ${lid} ${lid}.pend (liveStayC ${lid}) ${lid}_sp) = true := by decide +kernel
736
- theorem ${lid}_cover : checkSKStep ${id}_machine ${id}_proof (sliveStep ${lid} ${lid}.pend (liveCoverC ${lid}) ${lid}_cp) = true := by decide +kernel
737
- theorem ${lid}_helps : sliveHelpsOk ${id}_machine ${id}_proof ${lid} ${lid}_hp = true := by decide +kernel
738
- /-- ${l.name}: along every run that takes each helpful request infinitely often, \`from\` is followed by \`to\`. -/
739
- theorem ${lid}_holds (run : SRun ${id}_machine)
740
- (fair : ∀ h ∈ ${lid}.helps, ∀ n, ∃ m, n ≤ m ∧ ∀ p ∈ h.2, (run.req m).1 p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
741
- ∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
742
- sleads_sound ${id}_machine ${id}_proof ${lid} _ _ _ ${id}_check ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps run fair${wf}`;
692
+ (${a.tab}, [], ${a.tree}),
693
+ (${b.tab}, [], ${b.tree})⟩`;
743
694
  });
695
+ // A ghosted machine (compose.ts's ghost): the original and its ghosts, the machine Lean checks built from them
696
+ // (Ghost.lean), so the laws that read no ghost are laws of the original.
697
+ const orig = { ...m, state: Object.fromEntries(Object.entries(m.state).filter(([n2]) => !gv.includes(n2))), step: { ...m.step, state: Object.fromEntries(Object.entries(m.step.state ?? {}).filter(([n2]) => !gv.includes(n2))) } };
698
+ const h = {
699
+ step: sl => { const g = goalsS[allSteps.indexOf(sl)]; return { pr: enc(g, r.proof.trees[g.name]), inst: instOf(sl) }; },
700
+ sproof: sl => { const { pr, inst } = h.step(sl); return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, [], ${pr.tree}))`; },
701
+ allow: a => {
702
+ const f2 = allowed.get(a);
703
+ if (!f2)
704
+ return undefined;
705
+ const ar = f2.out, pr = enc(ar.goal, ar.tree), inst = ar.instances;
706
+ return `(${list(f2.wit.map(([inp, t]) => `(${ix(inp)}, ${e(t)})`))}, (${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, ${list(ar.goal.hyps.map(c))}, ${pr.tree})), ${list(ar.goal.zkeys.map(e))})`;
707
+ },
708
+ };
709
+ const records = claims.map(cl => cl.render(h)).filter(t => t.length);
744
710
  return `-- ${m.name}: ${laws.map(l => l.name).join('; ')}, with sets (carried ${carried.join(', ') || 'none'}; quantified flags ${quantified.join(', ') || 'none'})
745
711
  -- (variables ${vars.map((v2, j) => `${j} ${v2}`).join(', ')}; maps ${mapNames.map((v2, j) => `${j} ${v2}`).join(', ')})
746
712
  ${rf ? `def ${id}_refined : SRefined := ⟨${lit(rf.impl)},
747
713
  ${lit(rf.spec)},
748
714
  ${list(Object.entries(rf.wit).map(([inp, t]) => `(${ix(`b_${inp}`)}, ${e(t)})`))},
749
715
  ${c(rf.stutter)}⟩
750
- def ${id}_machine : SMachine := ${id}_refined.full` : `def ${id}_machine : SMachine := ${lit(m)}`}
716
+ def ${id}_machine : SMachine := ${id}_refined.full` : gv.length ? `def ${id}_ghosted : SGhosted := ⟨${lit(orig)},
717
+ ${list(gv.map(v2 => `(${ix(v2)}, ${e(m.state[v2])}, ${e(m.step.state?.[v2] ?? { op: 'var', name: v2 })})`))}⟩
718
+ def ${id}_machine : SMachine := ${id}_ghosted.full
719
+ theorem ${id}_ghostOk : sghostOk ${id}_ghosted = true := by decide +kernel` : `def ${id}_machine : SMachine := ${lit(m)}`}
751
720
  ${entries.join('\n')}
752
721
  def ${id}_proof : List SEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
753
722
  theorem ${id}_check : checkSMachine ${id}_machine ${id}_proof = true := by decide +kernel
@@ -756,7 +725,7 @@ theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_pro
756
725
  ${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${factsProof(id, m, mapNames, opts.simp)}${opts.tamper ? `
757
726
  -- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
758
727
  def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
759
- example : checkSMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${stepRecords.length ? `\n\n${stepRecords.join('\n\n')}` : ''}${refineRecord ? `\n\n${refineRecord}` : ''}${liveRecords.length ? `\n\n${liveRecords.join('\n\n')}` : ''}`;
728
+ example : checkSMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
760
729
  }
761
730
  // The facts, proven in Lean of the functions' Lean definitions, when every function has one: a run that starts with
762
731
  // those functions starts where SReach asks the facts to hold.
@@ -855,29 +824,23 @@ export function toLean(items, opts = {}) {
855
824
  }
856
825
  if (m.history && (m.fns || m.facts))
857
826
  throw new Error(`${item.id}: functions on a machine with a history aren't exported to Lean yet`);
858
- if (hasSets(m) || item.laws.some(l => l.keys?.length)) {
859
- // Sets (or functions, or laws about two keys): the laws and the step laws in one export (one proof of both).
860
- parts.push(`-- ${item.id}\n${item.lean?.defs ? `${item.lean.defs}\n` : ''}${smachineToLean(id, m, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}), ...(item.lean?.simp ? { simp: item.lean.simp } : {}) })}`);
861
- theorems.push(`${id}_laws`, ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
827
+ // One twin: a machine without a history goes to Lean as a machine with sets (one with none, if it has none): its laws,
828
+ // step laws, liveness and ghosts in one export. A history's laws go through History.lean, and its step laws and
829
+ // liveness through its plain machine (its sums as state, its queries as inputs).
830
+ if (m.history) {
831
+ parts.push(`-- ${item.id}\n${hmachineToLean(id, m, item.laws)}`);
832
+ theorems.push(`${id}_laws`);
833
+ if (item.stepLaws?.length || item.liveness?.length) {
834
+ parts.push(`-- ${item.id}: step laws and liveness, on its plain machine\n${smachineToLean(`${id}s`, historyMachine(m).machine, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}) })}`);
835
+ theorems.push(...(item.stepLaws ?? []).map((_, j) => `${id}s_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}s_live${j}_holds`));
836
+ }
862
837
  continue;
863
838
  }
864
- // A history's liveness through its plain machine (its sums as state), as its step laws are.
865
- if (item.liveness?.length && m.history) {
866
- parts.push(`-- ${item.id}: liveness, on its plain machine\n${mmachineToLean(`${id}s`, historyMachine(m).machine, item.laws, { liveness: item.liveness })}`);
867
- item.liveness.forEach((_, j) => theorems.push(`${id}s_live${j}_holds`));
868
- }
869
- const ghosted = item.ghosts?.length ? { ghosts: item.ghosts, stepLaws: item.stepLaws ?? [], ...(item.lean?.after ? { after: item.lean.after } : {}) } : {};
870
- parts.push(`-- ${item.id}\n${m.history ? hmachineToLean(id, m, item.laws) : mmachineToLean(id, m, item.laws, { ...(item.liveness?.length ? { liveness: item.liveness } : {}), ...ghosted })}`);
871
- theorems.push(`${id}_laws`, ...(m.history ? [] : (item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`)));
872
- if (item.ghosts?.length)
873
- item.stepLaws?.forEach((_, j) => theorems.push(`${id}_step${j}_holds`));
839
+ parts.push(`-- ${item.id}\n${item.lean?.defs ? `${item.lean.defs}\n` : ''}${smachineToLean(id, m, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}),
840
+ ...(item.ghosts?.length ? { ghosts: item.ghosts } : {}), ...(item.lean?.simp ? { simp: item.lean.simp } : {}), ...(item.lean?.after ? { after: item.lean.after } : {}) })}`);
841
+ theorems.push(`${id}_laws`, ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
874
842
  if (item.lean?.after)
875
843
  theorems.push(...[...item.lean.after(() => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1]));
876
- else if (item.stepLaws?.length) {
877
- const plain = m.history ? historyMachine(m).machine : m, sid = m.history ? `${id}s` : `${id}_steps`;
878
- parts.push(`-- ${item.id}: step laws\n${mmachineToLean(sid, plain, item.laws, { stepLaws: item.stepLaws })}`);
879
- item.stepLaws.forEach((_, j) => theorems.push(`${sid}_step${j}_holds`));
880
- }
881
844
  }
882
845
  else if ('refinement' in item) {
883
846
  const { impl, spec, relation, witness } = item.refinement;
@@ -886,7 +849,7 @@ export function toLean(items, opts = {}) {
886
849
  const laws = [...relation, { name: `${spec.name}'s precondition`, holds: spec.pre ?? atMost(n(0), n(0)) }];
887
850
  const sl = R.stepLaws[0] ?? { name: `${spec.name} allows the request it is sent`, then: { op: 'or', xs: [stutter, atMost(n(0), n(0))] } };
888
851
  const refined = { impl: S.impl, spec: S.spec, wit: witness, stutter, specPre: relation.length }, free = spec.inputs.map(i => `b_${i}`);
889
- parts.push(`-- ${item.id}\n${hasSets(R.machine) ? smachineToLean(id, R.machine, laws, { stepLaws: [sl], free, refined }) : mmachineToLean(id, R.machine, laws, { stepLaws: [sl], free, refined })}`);
852
+ parts.push(`-- ${item.id}\n${smachineToLean(id, R.machine, laws, { stepLaws: [sl], free, refined })}`);
890
853
  theorems.push(`${id}_laws`, `${id}_refines`);
891
854
  }
892
855
  else if ('loop' in item) {
@@ -902,7 +865,7 @@ export function toLean(items, opts = {}) {
902
865
  }
903
866
  // Sets.lean (which imports Steps.lean) only when a machine has sets.
904
867
  return `import History
905
- 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) ? 'Live' : items.some(item => 'machine' in item && (hasSets(item.machine) || item.laws.some(l => l.keys?.length))) ? 'Sets' : 'Steps'}
868
+ 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) ? 'Live' : items.some(item => 'machine' in item) ? 'Sets' : 'Steps'}
906
869
  open Kern Cert
907
870
  set_option maxRecDepth 100000
908
871
  set_option maxHeartbeats 0