gdp-wired 0.2.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.
@@ -106,6 +106,7 @@ function comparison(c, path) {
106
106
  export function truth(c, path) {
107
107
  switch (c.op) {
108
108
  case 'flag': throw new Error('flags are inlined before the kernel sees them');
109
+ case 'all': return undefined; // closed before a goal is checked; opaque here, so it gives a leaf nothing
109
110
  case 'not': {
110
111
  const t = truth(c.x, path);
111
112
  return t === undefined ? undefined : !t;
@@ -131,6 +132,7 @@ export function truth(c, path) {
131
132
  export function facts(c, path, positive = true) {
132
133
  switch (c.op) {
133
134
  case 'flag': throw new Error('flags are inlined before the kernel sees them');
135
+ case 'all': return [];
134
136
  case 'not': return facts(c.x, path, !positive);
135
137
  case 'and':
136
138
  case 'or': {
@@ -234,8 +236,8 @@ function natural(e) {
234
236
  default: return unreachable(e);
235
237
  }
236
238
  }
237
- const naturalC = (c) => c.op === 'flag' ? true : c.op === 'not' ? naturalC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.every(naturalC) : natural(c.a) && natural(c.b);
238
- function substE(e, s) {
239
+ const naturalC = (c) => c.op === 'flag' ? true : c.op === 'all' ? naturalC(c.c) : c.op === 'not' ? naturalC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.every(naturalC) : natural(c.a) && natural(c.b);
240
+ export function substE(e, s) {
239
241
  switch (e.op) {
240
242
  case 'var': return s.vars[e.name] ?? e;
241
243
  case 'const': return e;
@@ -262,6 +264,10 @@ export function substC(c, s) {
262
264
  }
263
265
  if (c.op === 'not')
264
266
  return { op: 'not', x: substC(c.x, s) };
267
+ if (c.op === 'all') {
268
+ const { [c.key]: _bound, ...vars } = s.vars;
269
+ return { op: 'all', key: c.key, c: substC(c.c, { ...s, vars }) };
270
+ }
265
271
  if (c.op === 'and' || c.op === 'or')
266
272
  return { op: c.op, xs: c.xs.map(y => substC(y, s)) };
267
273
  return { ...c, a: substE(c.a, s), b: substE(c.b, s) };
@@ -278,15 +284,19 @@ const identity = { vars: {}, maps: {}, flags: {} };
278
284
  const from = (at = identity) => ({ hyps: [], at });
279
285
  // Conditions that hold where the run is.
280
286
  const holds = (r, cs) => ({ hyps: [...r.hyps, ...cs.map(c => substC(c, r.at))], at: r.at });
281
- // Names read where the run is: inputs named for this step (a request's terms, or fresh names for a later step).
282
- const reading = (r, vars) => ({ hyps: r.hyps, at: { ...r.at, vars: { ...r.at.vars, ...vars } } });
287
+ // Names read where the run is: inputs named for this step (a request's terms, or fresh names for a later step), and the
288
+ // maps a request carries, renamed for a later step.
289
+ const reading = (r, vars, maps = {}) => ({ hyps: r.hyps, at: { ...r.at, vars: { ...r.at.vars, ...vars },
290
+ maps: { ...r.at.maps, ...Object.fromEntries(Object.entries(maps).map(([map, name]) => [map, (key) => ({ op: 'get', map: name, key })])) } } });
283
291
  // One step: each name the update sets is its term read where the run is; a map read after it is the written value at
284
- // the written key, and what the map held before elsewhere.
292
+ // the written key, and what the map held before elsewhere (or, written at every key, its value at the key read).
285
293
  const step = (r, u) => {
286
294
  const at = r.at, before = (map, key) => (at.maps[map] ? at.maps[map](key) : { op: 'get', map, key });
287
295
  return { hyps: r.hyps, at: {
288
296
  vars: { ...at.vars, ...Object.fromEntries(Object.entries(u.vars).map(([n, e]) => [n, substE(e, at)])) },
289
- maps: { ...at.maps, ...Object.fromEntries(Object.entries(u.maps).map(([map, w]) => [map, (key) => ({ op: 'if', c: { op: '=', a: key, b: substE(w.key, at) }, then: substE(w.value, at), else: before(map, key) })])) },
297
+ maps: { ...at.maps, ...Object.fromEntries(Object.entries(u.maps).map(([map, w]) => [map, 'each' in w
298
+ ? (key) => substE(w.value, { ...at, vars: { ...at.vars, [w.each]: key } })
299
+ : (key) => ({ op: 'if', c: { op: '=', a: key, b: substE(w.key, at) }, then: substE(w.value, at), else: before(map, key) })])) },
290
300
  flags: at.flags,
291
301
  } };
292
302
  };
@@ -300,17 +310,51 @@ const across = (r, c, state, maps, first) => substC(c, {
300
310
  flags: first.flags,
301
311
  });
302
312
  const goal = (name, r, conclusion) => ({ name, hyps: [...r.hyps, { op: 'not', x: conclusion }] });
303
- // A machine's step as an update, its flags inlined (they are decided before the step, over the frame it starts from).
313
+ // A machine's flags as its terms read them (decided before the step, over the frame it starts from): a plain flag its
314
+ // condition (through the flags before it), a quantified flag (one whose condition holds at every key) its 0/1 variable
315
+ // ?f, which `closing` ties to the condition.
316
+ const flagsOf = (m) => {
317
+ const out = {};
318
+ for (const [f, c] of Object.entries(m.step.flags ?? {}))
319
+ out[f] = c.op === 'all' ? { op: '=', a: { op: 'var', name: `?${f}` }, b: { op: 'const', value: 1 } } : substC(c, { vars: {}, maps: {}, flags: out });
320
+ return out;
321
+ };
322
+ const quantified = (m) => Object.entries(m.step.flags ?? {}).filter((e) => e[1].op === 'all');
323
+ // A machine's step as an update, its flags inlined.
304
324
  const updateOf = (m) => {
305
- const flags = { vars: {}, maps: {}, flags: m.step.flags ?? {} };
325
+ const flags = { vars: {}, maps: {}, flags: flagsOf(m) };
306
326
  return {
307
327
  vars: Object.fromEntries(Object.entries(m.step.state ?? {}).map(([n, e]) => [n, substE(e, flags)])),
308
- maps: Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([map, w]) => [map, { key: substE(w.key, flags), value: substE(w.value, flags) }])),
328
+ maps: Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([map, w]) => [map, 'each' in w ? { each: w.each, value: substE(w.value, flags) } : { key: substE(w.key, flags), value: substE(w.value, flags) }])),
309
329
  };
310
330
  };
311
- const inlineFlags = (m, c) => substC(c, { vars: {}, maps: {}, flags: m.step.flags ?? {} });
331
+ const inlineFlags = (m, c) => substC(c, { vars: {}, maps: {}, flags: flagsOf(m) });
332
+ // A quantified flag ?f, closed: it is 0 or 1; where it is 1, its condition holds at each key the proof names (instances
333
+ // under ?f); where it is 0, the condition fails at a fresh witness w?f. A real step decides ?f by the condition, and if
334
+ // that fails some key fails it, so these hold of every real step: a goal that refutes them refutes the machine.
335
+ const closing = (m, instances) => quantified(m).flatMap(([f, q]) => {
336
+ const v = { op: 'var', name: `?${f}` }, zero = { op: 'const', value: 0 }, one = { op: 'const', value: 1 };
337
+ const body = (key) => substC(q.c, { vars: { [q.key]: key }, maps: {}, flags: flagsOf(m) });
338
+ return [{ op: '<=', a: v, b: one }, ...(instances[`?${f}`] ?? []).map((key) => ({ op: 'or', xs: [{ op: '=', a: v, b: zero }, body(key)] })),
339
+ { op: 'or', xs: [{ op: '=', a: v, b: one }, { op: 'not', x: body({ op: 'var', name: `w?${f}` }) }] }];
340
+ });
341
+ // The names a later step (j >= 2) reads its request by: inputs, quantified flags and their witnesses, and carried maps.
342
+ const laterNames = (m, j) => ({
343
+ vars: Object.fromEntries([...m.inputs, ...quantified(m).flatMap(([f]) => [`?${f}`, `w?${f}`])].map(name => [name, { op: 'var', name: `${name}@${j}` }])),
344
+ maps: Object.fromEntries((m.inputMaps ?? []).map(map => [map, `${map}@${j}`])),
345
+ });
312
346
  // The laws assumed where the run is: each plain law, and each every-key law at the keys the proof names.
313
- const assumed = (laws, instances) => laws.flatMap(l => (l.forall ? (instances[l.name] ?? []).map(key => substC(l.holds, { vars: { [l.forall]: key }, maps: {}, flags: {} })) : [l.holds]));
347
+ const assumed = (laws, instances) => laws.flatMap(l => {
348
+ const keys = instances[l.name] ?? [];
349
+ if (l.keys?.length) {
350
+ const names = [l.forall, ...l.keys], n = names.length;
351
+ return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => substC(l.holds, { vars: Object.fromEntries(names.map((name, i) => [name, keys[t * n + i]])), maps: {}, flags: {} }));
352
+ }
353
+ return l.forall ? keys.map(key => substC(l.holds, { vars: { [l.forall]: key }, maps: {}, flags: {} })) : [l.holds];
354
+ });
355
+ // The facts about the functions, at the keys the proof names: as they are (they read the params and functions alone, so
356
+ // any frame reads them alike).
357
+ const given = (m, r, instances) => ({ hyps: [...r.hyps, ...assumed(m.facts ?? [], instances)], at: r.at });
314
358
  // The start: the state's start values over the params, every map cell its constant.
315
359
  const startOf = (m) => ({ vars: { ...m.state }, maps: Object.fromEntries(Object.entries(m.maps ?? {}).map(([map, c]) => [map, () => ({ op: 'const', value: c })])), flags: {} });
316
360
  function reads(t, out = { vars: new Set(), maps: new Set(), flags: new Set() }) {
@@ -326,6 +370,14 @@ function reads(t, out = { vars: new Set(), maps: new Set(), flags: new Set() })
326
370
  case 'flag':
327
371
  out.flags.add(t.name);
328
372
  break;
373
+ case 'all': {
374
+ const inner = reads(t.c);
375
+ inner.vars.delete(t.key);
376
+ for (const k of ['vars', 'maps', 'flags'])
377
+ for (const name of inner[k])
378
+ out[k].add(name);
379
+ break;
380
+ }
329
381
  case 'not':
330
382
  reads(t.x, out);
331
383
  break;
@@ -384,10 +436,10 @@ const distinct = (what, names) => { const seen = new Set(); for (const name of n
384
436
  // step reads params, inputs and the state before it; a flag reads the flags declared before it; a start value reads the
385
437
  // params; a law reads the params, the state, the maps and its own key.
386
438
  export function scope(m, laws = []) {
387
- const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), flags = Object.keys(m.step.flags ?? {});
439
+ const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), flags = Object.keys(m.step.flags ?? {}), carried = m.inputMaps ?? [], fns = Object.keys(m.fns ?? {});
388
440
  const names = [...m.params, ...m.inputs, ...state];
389
- distinct(m.name, [...names, ...maps]);
390
- for (const name of [...names, ...maps, ...flags])
441
+ distinct(m.name, [...names, ...maps, ...carried, ...fns]);
442
+ for (const name of [...names, ...maps, ...carried, ...fns, ...flags])
391
443
  plainName(m.name, name);
392
444
  for (const name of Object.keys(m.step.state ?? {}))
393
445
  if (!state.includes(name))
@@ -395,19 +447,168 @@ export function scope(m, laws = []) {
395
447
  for (const map of Object.keys(m.step.maps ?? {}))
396
448
  if (!maps.includes(map))
397
449
  throw new Error(`${m.name}'s step writes the map ${map}, which it doesn't declare`);
450
+ // The step reads the maps a request carries too. A key bound by a quantified flag or a write at every key is new and
451
+ // read only as a map's key (so every key the maps hold nothing at reads alike); a quantifier is a whole flag, nowhere
452
+ // else.
453
+ const readable = [...maps, ...carried, ...fns];
454
+ const bound = (what, key, t) => {
455
+ plainName(what, key);
456
+ if ([...names, ...readable, ...flags].includes(key))
457
+ throw new Error(`${what}: the key ${key} is already a name of ${m.name}`);
458
+ keyOnly(what, t, key, fns);
459
+ };
398
460
  inScope(`${m.name}'s start`, Object.values(m.state), m.params);
399
- Object.values(m.step.flags ?? {}).forEach((c, i) => inScope(`${m.name}'s flag ${flags[i]}`, [c], names, maps, flags.slice(0, i)));
400
- inScope(`${m.name}'s step`, [...Object.values(m.step.state ?? {}), ...Object.values(m.step.maps ?? {}).flatMap(w => [w.key, w.value])], names, maps, flags);
401
- if (m.assume)
402
- inScope(`${m.name}'s assumption`, [m.assume], names, maps, flags);
461
+ flags.forEach((f, i) => {
462
+ const c = m.step.flags[f], what = `${m.name}'s flag ${f}`;
463
+ // A quantified flag's body reads no flag (the twin decides quantified flags before the plain ones).
464
+ if (c.op === 'all') {
465
+ bound(what, c.key, c.c);
466
+ unquantified(what, c.c);
467
+ inScope(what, [c.c], [...names, c.key], readable, []);
468
+ }
469
+ else {
470
+ unquantified(what, c);
471
+ inScope(what, [c], names, readable, flags.slice(0, i));
472
+ }
473
+ });
474
+ for (const [map, w] of Object.entries(m.step.maps ?? {})) {
475
+ const what = `${m.name}'s write to ${map}`;
476
+ if ('each' in w) {
477
+ bound(what, w.each, w.value);
478
+ unquantified(what, w.value);
479
+ inScope(what, [w.value], [...names, w.each], readable, flags);
480
+ }
481
+ else {
482
+ unquantified(what, w.key);
483
+ unquantified(what, w.value);
484
+ inScope(what, [w.key, w.value], names, readable, flags);
485
+ }
486
+ }
487
+ for (const e of Object.values(m.step.state ?? {}))
488
+ unquantified(`${m.name}'s step`, e);
489
+ inScope(`${m.name}'s step`, Object.values(m.step.state ?? {}), names, readable, flags);
490
+ if (m.assume) {
491
+ unquantified(`${m.name}'s assumption`, m.assume);
492
+ inScope(`${m.name}'s assumption`, [m.assume], names, readable, flags);
493
+ }
403
494
  if (m.pre)
404
495
  inScope(`${m.name}'s precondition`, [m.pre], m.params);
405
- distinct(`${m.name}'s laws`, laws.map(l => l.name));
406
- for (const law of laws) {
407
- if (law.forall && [...names, ...maps].includes(law.forall))
408
- throw new Error(`law ${law.name}'s key ${law.forall} is already a name of ${m.name}`);
409
- inScope(`law ${law.name}`, [law.holds], [...m.params, ...state, ...(law.forall ? [law.forall] : [])], maps);
496
+ const facts = m.facts ?? [];
497
+ distinct(`${m.name}'s laws and facts`, [...laws, ...facts].map(l => l.name));
498
+ for (const law of [...laws, ...facts]) {
499
+ const what = facts.includes(law) ? `fact ${law.name}` : `law ${law.name}`;
500
+ for (const key of [law.forall, ...(law.keys ?? [])])
501
+ if (key && [...names, ...maps, ...carried, ...fns].includes(key))
502
+ throw new Error(`${what}'s key ${key} is already a name of ${m.name}`);
503
+ if (law.keys?.length && (!law.forall || new Set([law.forall, ...law.keys]).size !== law.keys.length + 1))
504
+ throw new Error(`${what}: further keys come with a first, each a different name`);
505
+ if (law.name.startsWith('?'))
506
+ throw new Error(`${what}: a name starting with ? is a flag's (its instances)`);
507
+ unquantified(what, law.holds);
410
508
  }
509
+ const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
510
+ for (const law of laws)
511
+ inScope(`law ${law.name}`, [law.holds], [...m.params, ...state, ...keysOf(law)], [...maps, ...fns]);
512
+ // A fact reads the params and the functions alone: what holds of them holds at every state.
513
+ for (const fact of facts)
514
+ inScope(`fact ${fact.name}`, [fact.holds], [...m.params, ...keysOf(fact)], fns);
515
+ }
516
+ // A key bound in t is read in key terms only: the key, or the key plus or less a term that doesn't read it (k + e, e + k,
517
+ // k ∸ e), each a map's key (no function's: a function's values don't stop where a map's cells do) or a side of a
518
+ // comparison whose other side doesn't read the key. Then t is constant between finitely many keys (the runtimes'
519
+ // breakpoints: each map's cells and each compared value, shifted by the offsets), so a runtime decides it on those.
520
+ const readsKey = (t, key) => JSON.stringify(t).includes(`"op":"var","name":${JSON.stringify(key)}`);
521
+ export const keyTerm = (e, key) => e.op === 'var' ? e.name === key
522
+ : e.op === 'add' ? (e.a.op === 'var' && e.a.name === key && !readsKey(e.b, key)) || (e.b.op === 'var' && e.b.name === key && !readsKey(e.a, key))
523
+ : e.op === 'sub' && e.a.op === 'var' && e.a.name === key && !readsKey(e.b, key);
524
+ function keyOnly(what, t, key, fns = []) {
525
+ const bad = () => { throw new Error(`${what}: the key ${key} is read only in a map's key or a comparison, alone or plus or less a term that doesn't read it`); };
526
+ const walk = (u) => {
527
+ switch (u.op) {
528
+ case 'var':
529
+ if (u.name === key)
530
+ bad();
531
+ break;
532
+ case 'get':
533
+ if (!(keyTerm(u.key, key) && !fns.includes(u.map)))
534
+ walk(u.key);
535
+ break;
536
+ case 'const':
537
+ case 'flag': break;
538
+ case 'not':
539
+ walk(u.x);
540
+ break;
541
+ case 'all':
542
+ walk(u.c);
543
+ break;
544
+ case 'and':
545
+ case 'or':
546
+ u.xs.forEach(walk);
547
+ break;
548
+ case 'mul':
549
+ walk(u.a);
550
+ break;
551
+ case 'if':
552
+ walk(u.c);
553
+ walk(u.then);
554
+ walk(u.else);
555
+ break;
556
+ case '<':
557
+ case '<=':
558
+ case '=':
559
+ if (keyTerm(u.a, key) && !readsKey(u.b, key))
560
+ walk(u.b);
561
+ else if (keyTerm(u.b, key) && !readsKey(u.a, key))
562
+ walk(u.a);
563
+ else {
564
+ walk(u.a);
565
+ walk(u.b);
566
+ }
567
+ break;
568
+ case 'add':
569
+ case 'sub':
570
+ case 'min':
571
+ case 'max':
572
+ case 'prod':
573
+ walk(u.a);
574
+ walk(u.b);
575
+ break;
576
+ default: unreachable(u);
577
+ }
578
+ };
579
+ walk(t);
580
+ }
581
+ // Whether the key is read only as a map's key, plainly (what the GPU searches decide: every cell, and one past them).
582
+ export const plainKey = (t, key) => {
583
+ const walk = (u) => {
584
+ switch (u.op) {
585
+ case 'var': return u.name !== key;
586
+ case 'get': return (u.key.op === 'var' && u.key.name === key) || walk(u.key);
587
+ case 'const':
588
+ case 'flag': return true;
589
+ case 'not': return walk(u.x);
590
+ case 'all': return walk(u.c);
591
+ case 'and':
592
+ case 'or': return u.xs.every(walk);
593
+ case 'mul': return walk(u.a);
594
+ case 'if': return walk(u.c) && walk(u.then) && walk(u.else);
595
+ case 'add':
596
+ case 'sub':
597
+ case 'min':
598
+ case 'max':
599
+ case 'prod':
600
+ case '<':
601
+ case '<=':
602
+ case '=': return walk(u.a) && walk(u.b);
603
+ default: return unreachable(u);
604
+ }
605
+ };
606
+ return walk(t);
607
+ };
608
+ // No quantifier inside t: a quantifier is a whole flag.
609
+ function unquantified(what, t) {
610
+ if (JSON.stringify(t).includes('"op":"all"'))
611
+ throw new Error(`${what}: a condition at every key is a flag of its own (flags: {name: all(...)})`);
411
612
  }
412
613
  // --- histories ---------------------------------------------------------------------------------------------------------
413
614
  // A term or condition that reads only these names, and no map.
@@ -422,6 +623,8 @@ export function historyMachine(m) {
422
623
  const h = m.history;
423
624
  if (!h)
424
625
  return { machine: m, goals: [] };
626
+ if (m.inputMaps?.length || quantified(m).length || Object.values(m.step.maps ?? {}).some(w => 'each' in w))
627
+ throw new Error(`${m.name}: a machine with a history has no maps carried by requests, quantified flags or writes at every key`);
425
628
  const queries = Object.keys(h.queries), sums = Object.keys(h.sums), plainInputs = m.inputs.filter(i => !queries.includes(i));
426
629
  const fields = h.record.map(f => `r.${f}`);
427
630
  const taken = [...m.params, ...m.inputs, ...Object.keys(m.state), ...Object.keys(m.step.state ?? {})];
@@ -464,17 +667,20 @@ export function obligations(m, laws, instances = {}) {
464
667
  return [...obligations(machine, laws, instances), ...goals];
465
668
  }
466
669
  scope(m, laws);
467
- const ok = [...Object.values(m.state), ...Object.values(m.step.state ?? {}), ...Object.values(m.step.maps ?? {}).flatMap(w => [w.key, w.value])].every(natural)
670
+ const ok = [...Object.values(m.state), ...Object.values(m.step.state ?? {}), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.value] : [w.key, w.value]))].every(natural)
468
671
  && Object.values(m.step.flags ?? {}).every(naturalC) && laws.every(l => naturalC(l.holds)) && Object.values(m.maps ?? {}).every(c => Number.isInteger(c) && c >= 0);
469
- if (!ok || laws.some(l => mentions(l.holds, m.inputs)))
672
+ if (!ok || !(m.facts ?? []).every(l => naturalC(l.holds)) || laws.some(l => mentions(l.holds, m.inputs)))
470
673
  throw new Error('a negative constant or factor, or a law about a request, is outside the kernel');
471
674
  // A law that holds at every key is proven at a fresh one.
675
+ // A law about every two keys keeps its second key as it is: a fresh name nothing else reads, so the goal is about every
676
+ // value of it too (the Lean twin reads it so).
472
677
  const proven = (law) => (law.forall ? substC(law.holds, { vars: { [law.forall]: { op: 'var', name: `${law.forall}#` } }, maps: {}, flags: {} }) : law.holds);
473
678
  const pre = preOf(m), u = updateOf(m), assume = m.assume ? [inlineFlags(m, m.assume)] : [];
474
679
  return laws.flatMap(law => {
475
680
  // At the start: the precondition, and no request. One step: from a state where the laws hold, a request allowed.
476
- const start = holds(from(startOf(m)), pre);
477
- const turn = step(holds(from(), [...pre, ...assume, ...assumed(laws, instances[law.name] ?? {})]), u);
681
+ // The facts hold throughout, read where the run begins (they read no state).
682
+ const start = given(m, holds(from(startOf(m)), pre), instances[law.name] ?? {});
683
+ const turn = step(holds(given(m, from(), instances[law.name] ?? {}), [...pre, ...assume, ...assumed(laws, instances[law.name] ?? {}), ...closing(m, instances[law.name] ?? {})]), u);
478
684
  return [goal(`${law.name} @start`, start, atEnd(start, proven(law))), goal(`${law.name} @step`, turn, atEnd(turn, proven(law)))];
479
685
  });
480
686
  }
@@ -500,7 +706,7 @@ export function loopObligations(l, laws, rank) {
500
706
  inScope(`${l.name}'s rank and laws`, [...ranks, ...laws.map(law => law.holds)], [...l.params, ...Object.keys(l.state)]);
501
707
  if (ranks.some(r => mentions({ op: '=', a: r, b: r }, l.inputs)))
502
708
  throw new Error('a rank reads the state and params, not the turn\'s input');
503
- if (!parts.every(natural) || !naturalC(l.while) || !laws.every(law => !law.forall && naturalC(law.holds) && !mentions(law.holds, l.inputs)))
709
+ if (!parts.every(natural) || !naturalC(l.while) || !laws.every(law => !law.forall && !law.keys?.length && naturalC(law.holds) && !mentions(law.holds, l.inputs)))
504
710
  throw new Error('outside the kernel');
505
711
  const pre = preOf(l);
506
712
  const start = holds(from({ vars: { ...l.state }, maps: {}, flags: {} }), pre);
@@ -527,15 +733,59 @@ export function progressObligations(m, laws, instances, p) {
527
733
  if (mentions({ op: '=', a: e, b: e }, m.inputs))
528
734
  throw new Error('a request is named from the state and params');
529
735
  const req = { vars: p.request, maps: {}, flags: {} };
530
- const flags = Object.fromEntries(Object.entries(m.step.flags ?? {}).map(([f, c]) => [f, substC(c, req)]));
531
- // The run: a state where the laws and `when` hold, then the step the named request causes.
532
- const before = holds(from(), [...preOf(m), ...assumed(laws, instances), p.when]);
533
- const caused = step(reading(before, p.request), updateOf(m));
736
+ const flags = Object.fromEntries(Object.entries(flagsOf(m)).map(([f, c]) => [f, substC(c, req)]));
737
+ // The run: a state where the laws and `when` hold, the named request (its quantified flags closed), then the step it
738
+ // causes. Maps a request carries are left as any maps: the step is shown for all of them.
739
+ const before = holds(given(m, from(), instances), [...preOf(m), ...assumed(laws, instances), p.when]);
740
+ const asked = holds(reading(before, p.request), closing(m, instances));
741
+ const caused = step(asked, updateOf(m));
534
742
  return [
535
- ...(m.assume ? [goal(`${p.name}: the request is allowed`, before, substC(m.assume, req))] : []),
743
+ ...(m.assume ? [goal(`${p.name}: the request is allowed`, asked, substC(inlineFlags(m, m.assume), req))] : []),
536
744
  goal(`${p.name}: the step it causes`, caused, across(caused, p.then, Object.keys(m.state), Object.keys(m.maps ?? {}), { inputs: p.request, flags })),
537
745
  ];
538
746
  }
747
+ const zero = { op: 'const', value: 0 };
748
+ export const trueC = { op: '<=', a: zero, b: zero }, falseC = { op: '<', a: zero, b: zero };
749
+ const and2 = (a, b) => ({ op: 'and', xs: [a, b] }), or2 = (a, b) => ({ op: 'or', xs: [a, b] });
750
+ const lexLt = (after, before) => (after.length && before.length ? or2({ op: '<', a: after[0], b: before[0] }, and2({ op: '=', a: after[0], b: before[0] }, lexLt(after.slice(1), before.slice(1)))) : falseC);
751
+ const eqs = (after, before) => (after.length && before.length ? and2({ op: '=', a: after[0], b: before[0] }, eqs(after.slice(1), before.slice(1))) : trueC);
752
+ const keep = (ws, prime) => (ws.length ? and2(or2({ op: 'not', x: ws[0] }, prime(ws[0])), keep(ws.slice(1), prime)) : trueC);
753
+ const anyOf = (ws) => (ws.length ? or2(ws[0], anyOf(ws.slice(1))) : falseC);
754
+ const named = (req) => (req.length ? and2({ op: '=', a: { op: 'var', name: req[0][0] }, b: req[0][1] }, named(req.slice(1))) : trueC);
755
+ export const livenessRanks = (l) => (Array.isArray(l.rank) ? l.rank : [l.rank]);
756
+ export const livenessHelpful = (l) => {
757
+ if ((l.request === undefined) === (l.helpful === undefined))
758
+ throw new Error(`liveness ${l.name}: a helpful request, or helpful requests with their regions`);
759
+ return (l.helpful ?? [{ request: l.request }]).map(h => ({ when: h.when ?? trueC, request: h.request }));
760
+ };
761
+ export function livenessParts(m, l) {
762
+ // A machine with a history as its plain machine (its sums are state, its queries inputs no request names).
763
+ const plain = m.history ? historyMachine(m).machine : m;
764
+ const queries = Object.keys(m.history?.queries ?? {}), client = plain.inputs.filter(i => !queries.includes(i));
765
+ const ranks = livenessRanks(l), helpful = livenessHelpful(l);
766
+ if (!ranks.length || !helpful.length)
767
+ throw new Error(`liveness ${l.name}: a rank of one natural or more, and a helpful request or more`);
768
+ for (const [what, t] of [['from', l.from], ['to', l.to], ...ranks.map((r, i) => [`rank ${i + 1}`, { op: '=', a: r, b: r }]), ...helpful.map((h, i) => [`region ${i + 1}`, h.when])]) {
769
+ if (mentions(t, plain.inputs))
770
+ throw new Error(`liveness ${l.name}: ${what} reads the state and params, not a request`);
771
+ }
772
+ for (const h of helpful)
773
+ for (const [input, t] of Object.entries(h.request)) {
774
+ if (!client.includes(input))
775
+ throw new Error(`liveness ${l.name}: a helpful request names inputs a client sends (${input} is ${queries.includes(input) ? 'a query, computed from the log' : 'none'})`);
776
+ if (mentions({ op: '=', a: t, b: t }, plain.inputs))
777
+ throw new Error(`liveness ${l.name}: the request's ${input} is a term over the state and params`);
778
+ }
779
+ const after = { vars: Object.fromEntries(Object.keys(plain.state).map(n => [n, { op: 'var', name: `${n}'` }])), maps: Object.fromEntries(Object.keys(plain.maps ?? {}).map(map => [map, (key) => ({ op: 'get', map: `${map}'`, key })])), flags: {} };
780
+ const prime = (c) => substC(c, after), ranks2 = ranks.map(r => substE(r, after)), to2 = prime(l.to);
781
+ const pending = and2(l.from, { op: 'not', x: l.to }), lower = lexLt(ranks2, ranks), regions = helpful.map(h => h.when);
782
+ return {
783
+ stay: { name: `${l.name}: each step stays or gets there, the rank kept only with its region`, when: pending,
784
+ then: and2(or2(prime(l.from), to2), or2(to2, or2(lower, and2(eqs(ranks2, ranks), keep(regions, prime))))) },
785
+ cover: { name: `${l.name}: some helpful request applies`, when: pending, then: anyOf(regions) },
786
+ helps: helpful.map((h, i) => ({ name: `${l.name}: helpful request ${i + 1}`, when: and2(pending, and2(h.when, named(Object.entries(h.request)))), then: or2(to2, lower) })),
787
+ };
788
+ }
539
789
  export function stepLawObligations(machine, laws, instances, sl) {
540
790
  const m = machine.history ? historyMachine(machine).machine : machine;
541
791
  scope(m, laws);
@@ -545,35 +795,43 @@ export function stepLawObligations(machine, laws, instances, sl) {
545
795
  const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
546
796
  if (whens.length > k)
547
797
  throw new Error(`step law ${sl.name}: one when per step at most`);
548
- const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), flagNames = Object.keys(m.step.flags ?? {});
798
+ const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), carried = m.inputMaps ?? [], flagNames = Object.keys(m.step.flags ?? {});
549
799
  const primes = [...state, ...maps].map(n => `${n}'`);
550
- const later = Array.from({ length: k - 1 }, (_, i) => m.inputs.map(input => `${input}@${i + 2}`)).flat();
551
- const taken = [...m.params, ...m.inputs, ...state, ...maps];
552
- for (const p of [...primes, ...later])
800
+ const laters = Array.from({ length: k - 1 }, (_, i) => laterNames(m, i + 2));
801
+ const later = laters.flatMap(l => Object.values(l.vars).map(e => e.name)), laterMaps = laters.flatMap(l => Object.values(l.maps));
802
+ const taken = [...m.params, ...m.inputs, ...state, ...maps, ...carried];
803
+ for (const p of [...primes, ...later, ...laterMaps])
553
804
  if (taken.includes(p))
554
805
  throw new Error(`${m.name}: ${p} is a name of the machine and a name a step law reads`);
555
- if (sl.forall && [...taken, ...primes, ...later].includes(sl.forall))
556
- throw new Error(`step law ${sl.name}'s key ${sl.forall} is already a name of ${m.name}`);
557
- inScope(`step law ${sl.name}`, [sl.then], [...taken, ...state.map(n => `${n}'`), ...later, ...(sl.forall ? [sl.forall] : [])], [...maps, ...maps.map(n => `${n}'`)], flagNames);
558
- for (const w of whens)
559
- inScope(`step law ${sl.name}'s when`, [w], taken, maps, flagNames);
806
+ for (const key of [sl.forall, ...(sl.keys ?? [])])
807
+ if (key && [...taken, ...primes, ...later].includes(key))
808
+ throw new Error(`step law ${sl.name}'s key ${key} is already a name of ${m.name}`);
809
+ if (sl.keys?.length && (!sl.forall || new Set([sl.forall, ...sl.keys]).size !== sl.keys.length + 1))
810
+ throw new Error(`step law ${sl.name}: further keys come with a first, each a different name`);
811
+ unquantified(`step law ${sl.name}`, sl.then);
812
+ inScope(`step law ${sl.name}`, [sl.then], [...taken, ...state.map(n => `${n}'`), ...later, ...(sl.forall ? [sl.forall] : []), ...(sl.keys ?? [])], [...maps, ...carried, ...maps.map(n => `${n}'`), ...laterMaps], flagNames);
813
+ for (const w of whens) {
814
+ unquantified(`step law ${sl.name}'s when`, w);
815
+ inScope(`step law ${sl.name}'s when`, [w], taken, [...maps, ...carried], flagNames);
816
+ }
560
817
  if (!naturalC(sl.then) || !whens.every(naturalC))
561
818
  throw new Error(`step law ${sl.name}: a negative constant or factor is outside the kernel`);
562
819
  const u = updateOf(m), assume = m.assume ? [inlineFlags(m, m.assume)] : [];
563
- let run = holds(from(), preOf(m));
820
+ let run = given(m, holds(from(), preOf(m)), instances);
564
821
  for (let j = 1; j <= k; j++) {
565
822
  if (j > 1)
566
- run = reading(run, Object.fromEntries(m.inputs.map(input => [input, { op: 'var', name: `${input}@${j}` }])));
567
- run = step(holds(run, [...assumed(laws, instances), ...assume, ...(whens[j - 1] ? [inlineFlags(m, whens[j - 1])] : [])]), u);
823
+ run = reading(run, laters[j - 2].vars, laters[j - 2].maps);
824
+ run = step(holds(run, [...assumed(laws, instances), ...assume, ...(whens[j - 1] ? [inlineFlags(m, whens[j - 1])] : []), ...closing(m, instances)]), u);
568
825
  }
569
- return [goal(k === 1 ? `${sl.name} @step law` : `${sl.name} @${k} steps`, run, across(run, sl.then, state, maps, { inputs: {}, flags: m.step.flags ?? {} }))];
826
+ return [goal(k === 1 ? `${sl.name} @step law` : `${sl.name} @${k} steps`, run, across(run, sl.then, state, maps, { inputs: {}, flags: flagsOf(m) }))];
570
827
  }
571
828
  // The laws' proof and every step law's tree: all must check.
829
+ // `perStep`: a step law's own instances, where it has them (else `instances`).
572
830
  export function checkStepLaws(m, laws, stepLaws, proof) {
573
- return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => stepLawObligations(m, laws, proof.instances, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name])));
831
+ return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => stepLawObligations(m, laws, proof.perStep?.[sl.name] ?? proof.instances, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name])));
574
832
  }
575
833
  export function foldObligations(f, laws) {
576
- if (![...Object.values(f.leaf), ...Object.values(f.node)].every(natural) || !laws.every(law => !law.forall && naturalC(law.holds) && !mentions(law.holds, f.inputs)))
834
+ if (![...Object.values(f.leaf), ...Object.values(f.node)].every(natural) || !laws.every(law => !law.forall && !law.keys?.length && naturalC(law.holds) && !mentions(law.holds, f.inputs)))
577
835
  throw new Error('outside the kernel');
578
836
  const names = Object.keys(f.leaf);
579
837
  distinct(f.name, [...f.params, ...f.inputs, ...names]);
@@ -1,5 +1,5 @@
1
- import type { Law, Machine } from '../core/machine.js';
2
- import { type Goal, type Instances, type Path, type StepLaw, type Tree } from './kernel.js';
1
+ import type { C, E, Law, Machine } from '../core/machine.js';
2
+ import { type Goal, type Instances, type Liveness, type Path, type StepLaw, type Tree } from './kernel.js';
3
3
  export type Outcome = {
4
4
  tree: Tree;
5
5
  nodes: number;
@@ -24,20 +24,44 @@ export type Report = {
24
24
  checked: boolean;
25
25
  };
26
26
  export declare function proveLaws(m: Machine, laws: Law[]): Report & {
27
+ laws?: Law[];
27
28
  proof?: {
28
29
  instances: Record<string, Instances>;
29
30
  trees: Record<string, Tree>;
30
31
  };
31
32
  };
32
- export declare function proveStepLaws(m: Machine, laws: Law[], stepLaws: StepLaw[]): Report & {
33
- proof?: {
34
- laws: {
35
- instances: Record<string, Instances>;
36
- trees: Record<string, Tree>;
37
- };
38
- instances: Instances;
33
+ type StepProof = {
34
+ laws: {
35
+ instances: Record<string, Instances>;
39
36
  trees: Record<string, Tree>;
40
37
  };
38
+ instances: Instances;
39
+ perStep?: Record<string, Instances>;
40
+ trees: Record<string, Tree>;
41
+ };
42
+ export declare function proveStepLaws(m: Machine, given: Law[], stepLaws: StepLaw[]): Report & {
43
+ laws?: Law[];
44
+ stepLaws?: StepLaw[];
45
+ proof?: StepProof;
46
+ };
47
+ export declare function proveProgress(m: Machine, laws: Law[], p: {
48
+ name: string;
49
+ when: C;
50
+ then: C;
51
+ request: Record<string, E>;
52
+ }): Report & {
53
+ instances?: Instances;
54
+ trees?: Record<string, Tree>;
55
+ };
56
+ export declare function proveLiveness(m: Machine, laws: Law[], l: Liveness): Report & {
57
+ steps?: ReturnType<typeof proveStepLaws>;
41
58
  };
42
59
  export declare const countLeaves: (t: Tree) => number;
43
60
  export declare function concretize(goal: Goal, bound?: number): Record<string, number> | undefined;
61
+ export declare function proveBuilt(m: Machine, laws: Law[], build: (inst: Instances) => Goal): {
62
+ proven: boolean;
63
+ instances?: Instances;
64
+ goal?: Goal;
65
+ tree?: Tree;
66
+ };
67
+ export {};