gdp-wired 0.19.0 → 0.20.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.
@@ -83,8 +83,8 @@ function linearCands(target, forms, samples, show) {
83
83
  return dedupe(out.filter(c => samples.every(s => holds(c, s))));
84
84
  }
85
85
  const dedupe = (cs) => [...new Map(cs.map(c => [c.name, c])).values()];
86
- // A polynomial in x of degree 2 at most, exact over the rationals, as d·f = a·x·x + b·x + c with whole numbers: fitted to
87
- // three points, kept when every sample meets it.
86
+ // A polynomial in x of degree 3 at most, exact over the rationals, as d·f = a·x·x·x + b·x·x + c·x + e with whole
87
+ // numbers: fitted to four points (Newton's differences), kept when every sample meets it.
88
88
  function closedForm(samples, show) {
89
89
  const at = new Map();
90
90
  for (const s of samples) {
@@ -95,22 +95,30 @@ function closedForm(samples, show) {
95
95
  if (![0, 1, 2].every(x => at.has(x)))
96
96
  return [];
97
97
  const [f0, f1, f2] = [at.get(0), at.get(1), at.get(2)];
98
+ const keep = (lin) => {
99
+ const g = Object.values(lin).reduce((p, q) => gcd(p, Math.abs(q)), 0);
100
+ const c = { name: '', rel: '=', lin: Object.fromEntries(Object.entries(lin).map(([a, k]) => [a, k / g]).filter(([, k]) => k !== 0)) };
101
+ c.name = nameOf(c, show, 'f');
102
+ return samples.every(s => holds(c, s)) ? [c] : [];
103
+ };
98
104
  // f = a x² + b x + c: c = f0, a = (f2 - 2 f1 + f0) / 2, b = f1 - f0 - a; times 2 to keep whole numbers.
99
105
  const A = f2 - 2 * f1 + f0, B = 2 * (f1 - f0) - A, C = 2 * f0;
100
- if (A === 0)
101
- return []; // linear: the bounds find it
102
- const g = [A, B, C, 2].reduce((p, q) => gcd(p, Math.abs(q)), 0);
103
- const c = { name: '', rel: '=', lin: Object.fromEntries(Object.entries({ f: 2 / g, xx: -A / g, x: -B / g, 1: -C / g }).filter(([, k]) => k !== 0)) };
104
- c.name = nameOf(c, show, 'f');
105
- return samples.every(s => holds(c, s)) ? [c] : [];
106
+ const square = A === 0 ? [] : keep({ f: 2, xx: -A, x: -B, 1: -C });
107
+ if (square.length || !at.has(3))
108
+ return square;
109
+ // f = Δ0 + Δ1·x + Δ2·x(x − 1)/2 + Δ3·x(x − 1)(x − 2)/6, times 6: x³ by Δ3, x² by 3Δ2 − 3Δ3, x by 6Δ1 − 3Δ2 + 2Δ3.
110
+ const d1 = f1 - f0, d2 = f2 - 2 * f1 + f0, d3 = at.get(3) - 3 * f2 + 3 * f1 - f0;
111
+ if (d3 === 0)
112
+ return []; // degree 2 at most: above, or linear (the bounds find it)
113
+ return keep({ f: 6, xxx: -d3, xx: -(3 * d2 - 3 * d3), x: -(6 * d1 - 3 * d2 + 2 * d3), 1: -6 * f0 });
106
114
  }
107
115
  // The law a goal is of (the kernel names each goal after its law: '<law> @step').
108
116
  const lawOfGoal = (goal) => goal.replace(/ @(start|step|step law|\d+ steps|leaf|node)$/, '');
109
117
  const isGoalOf = (goal) => (c) => { const law = lawOfGoal(goal); return law === c.name || law === `${c.name} at y`; };
110
118
  const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
111
119
  // --- recurrences ---------------------------------------------------------------------------------------------------
112
- const showRecur = (a) => (a === 'f' ? 'f(x)' : a === 'g' ? 'f(y)' : a === 'xx' ? 'x·x' : a);
113
- const srcRecur = (a) => (a === 'xx' ? 'x.mul(x)' : a);
120
+ const showRecur = (a) => (a === 'f' ? 'f(x)' : a === 'g' ? 'f(y)' : a === 'xx' ? 'x·x' : a === 'xxx' ? 'x·x·x' : a);
121
+ const srcRecur = (a) => (a === 'xx' ? 'x.mul(x)' : a === 'xxx' ? 'x.mul(x).mul(x)' : a);
114
122
  // The function's values: x from 0 to 40 at a few values of each further argument (all of them for two at most).
115
123
  function recurSamples(d, args) {
116
124
  const f = (typeof d.js === 'function' ? d.js : compileFunction([], `return (${d.js})`)());
@@ -128,7 +136,7 @@ function recurSamples(d, args) {
128
136
  break;
129
137
  throw e;
130
138
  }
131
- out.push({ x, f: y, xx: x * x, ...Object.fromEntries(args.map((a, i) => [a, t[i]])) });
139
+ out.push({ x, f: y, xx: x * x, xxx: x * x * x, ...Object.fromEntries(args.map((a, i) => [a, t[i]])) });
132
140
  }
133
141
  return out;
134
142
  }
@@ -167,9 +175,9 @@ function recurCands(samples, args) {
167
175
  c.name = nameOf(c, showRecur, 'g');
168
176
  return { lemmas, between };
169
177
  }
170
- const recurAtoms = (args, x, f, env, y, g) => ({ x, f, xx: x.mul(x), ...(y && g ? { y, g } : {}), ...Object.fromEntries(args.map(a => [a, env[a]])) });
178
+ const recurAtoms = (args, x, f, env, y, g) => ({ x, f, xx: x.mul(x), xxx: x.mul(x).mul(x), ...(y && g ? { y, g } : {}), ...Object.fromEntries(args.map(a => [a, env[a]])) });
171
179
  const recurSrc = (c, args, between) => {
172
- const reads = (a) => Object.keys(c.lin).includes(a) || (a === 'x' && 'xx' in c.lin);
180
+ const reads = (a) => Object.keys(c.lin).includes(a) || (a === 'x' && ('xx' in c.lin || 'xxx' in c.lin));
173
181
  const names = between ? ['x', 'f', 'y', 'g'] : ['x', 'f'];
174
182
  let unused = 0;
175
183
  const params = [...names.map(n => (reads(n) ? n : '_'.repeat(++unused))), ...(args.some(reads) ? [`{${args.filter(reads).join(', ')}}`] : [])];
@@ -268,27 +276,40 @@ function discoverSearch(r, laws, stepLaws, first) {
268
276
  if (first.proven && first.checked)
269
277
  return { verdict: first, rule: r, found: {} };
270
278
  const { params, fns, lists } = r.parts, ps = natParams(params);
271
- // Per function and per list: what is proven, made into the rule.
272
- const fnMade = {};
273
- for (const [n, d] of fns) {
274
- const src = recurOf(d);
275
- if (!src)
276
- continue;
277
- const args = [...(src.def.args ?? [])], { lemmas, between } = recurCands(recurSamples(d, args), args);
278
- const made = houdiniRecur(d, lemmas, between);
279
- if (made && (made.lemmas.length || made.between.length))
280
- fnMade[n] = made;
281
- }
282
- let rule = r.withParts(Object.fromEntries(Object.entries(fnMade).map(([n, m]) => [n, m.d])));
283
- const listKept = {};
284
- for (const part of lists) {
285
- const out = houdiniList(rule, part, listCands(part, params, listSamples(part, params)), ps);
286
- if (out && out.kept.length) {
287
- rule = out.rule;
288
- listKept[part[0]] = out.kept;
279
+ // Per function and per list: what is proven, made into the rule. A function with a closed form (of degree 2 or 3,
280
+ // fitted exactly) first with it alone: it decides the function, and the bounds beside it, which the closed form
281
+ // implies only through products, are slow to prove by induction; the rest where the laws aren't proven with it.
282
+ const cands = new Map(fns.flatMap(([n, d]) => { const src = recurOf(d); if (!src)
283
+ return []; const args = [...(src.def.args ?? [])]; return [[n, recurCands(recurSamples(d, args), args)]]; }));
284
+ const exact = (c) => c.rel === '=' && ('xx' in c.lin || 'xxx' in c.lin);
285
+ const makeFns = (closedOnly) => {
286
+ const out = {};
287
+ for (const [n, d] of fns) {
288
+ const cs = cands.get(n);
289
+ if (!cs)
290
+ continue;
291
+ const closed = cs.lemmas.filter(exact), made = closedOnly && closed.length ? houdiniRecur(d, closed, []) : houdiniRecur(d, cs.lemmas, cs.between);
292
+ if (made && (made.lemmas.length || made.between.length))
293
+ out[n] = made;
289
294
  }
290
- }
291
- const verdict = proveSteps(rule, laws, stepLaws, { search: false });
295
+ return out;
296
+ };
297
+ const withLists = (fnMade) => {
298
+ let rule = r.withParts(Object.fromEntries(Object.entries(fnMade).map(([n, m]) => [n, m.d])));
299
+ const listKept = {};
300
+ for (const part of lists) {
301
+ const out = houdiniList(rule, part, listCands(part, params, listSamples(part, params)), ps);
302
+ if (out && out.kept.length) {
303
+ rule = out.rule;
304
+ listKept[part[0]] = out.kept;
305
+ }
306
+ }
307
+ return { fnMade, rule, listKept, verdict: proveSteps(rule, laws, stepLaws, { search: false }) };
308
+ };
309
+ let made = [...cands.values()].some(cs => cs.lemmas.some(exact)) ? withLists(makeFns(true)) : undefined;
310
+ if (!made || !(made.verdict.proven && made.verdict.checked))
311
+ made = withLists(makeFns(false));
312
+ const { fnMade, rule, listKept, verdict } = made;
292
313
  const found = () => ({
293
314
  ...Object.fromEntries(Object.entries(fnMade).map(([n, m]) => { const args = [...(recurOf(m.d).def.args ?? [])]; return [n, { ...(m.lemmas.length ? { lemmas: m.lemmas.map(c => recurSrc(c, args, false)) } : {}), ...(m.between.length ? { between: m.between.map(c => recurSrc(c, args, true)) } : {}) }]; })),
294
315
  ...Object.fromEntries(Object.entries(listKept).map(([n, cs]) => [n, { lemmas: cs.map(c => listSrc(c, Object.keys(lists.find(l => l[0] === n)[2]), ps, c.target)) }])),
@@ -594,7 +594,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
594
594
  leadsTo(name: string, spec: {
595
595
  from: (env: P & S & M & Su) => Cond;
596
596
  to: (env: P & S & M & Su) => Cond;
597
- rank: (env: P & S & M & Su) => TermLike | TermLike[];
597
+ rank?: (env: P & S & M & Su) => TermLike | TermLike[];
598
598
  helpful: ((env: P & S & M & Su) => Record<string, TermLike>) | {
599
599
  when?: (env: P & S & M & Su) => Cond;
600
600
  request: (env: P & S & M & Su) => Record<string, TermLike>;
@@ -828,7 +828,9 @@ export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj
828
828
  export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[], opts?: ProveOptions): Verdict;
829
829
  export type { StepLaw };
830
830
  export type { Liveness };
831
- export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness): Verdict;
831
+ export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness, opts?: {
832
+ ms?: number;
833
+ }): Verdict;
832
834
  export declare const proveDecision: (d: Decision) => Verdict;
833
835
  export declare function crossCheckDecision(d: Decision, opts?: {
834
836
  trials?: number;
package/dist/core/dsl.js CHANGED
@@ -19,10 +19,10 @@ import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersO
19
19
  import { allowed, keysOf, requestEnv, watch } from "./watch.js";
20
20
  import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
21
21
  import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws, usualProof, withDeadline } from "../kernel/prove.js";
22
- import { certCyclic, certOn, crossCyclic, cyclicOn, cycLog, foldNodes, proveCyclic, quietly, recurNodes, translatedProof } from "./cyclic.js";
22
+ import { certCyclic, certOn, crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes, translatedProof, uncovered } from "./cyclic.js";
23
23
  import { discover } from "./discover.js";
24
24
  import { inferLaws } from "./infer.js";
25
- import { cycleLemmas, findRank } from "./cycles.js";
25
+ import { cycleLemmas, findLeadsTo, findRank } from "./cycles.js";
26
26
  // --- terms and conditions -------------------------------------------------------------------------------------------
27
27
  const naturalConst = (v) => {
28
28
  if (!Number.isSafeInteger(v) || v < 0)
@@ -1430,8 +1430,12 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1430
1430
  lemmaProof = usualProof.last;
1431
1431
  if (cyclicOn())
1432
1432
  crossCyclic('recurrence', name, verdict.proven && verdict.checked, () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0, translatedRecur({ lemmas: lemmaProof }));
1433
- if (certOn() && verdict.proven && verdict.checked)
1434
- verdict = { ...verdict, checked: certCyclic('recurrence', name, translatedRecur({ lemmas: lemmaProof }), () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0).checked };
1433
+ // (the usual trees checked already, inside quietly: where no cyclic proof is made they are the certificate)
1434
+ if (certOn() && verdict.proven && verdict.checked) {
1435
+ const c = certCyclic('recurrence', name, translatedRecur({ lemmas: lemmaProof }), () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0);
1436
+ if (!c.checked)
1437
+ uncovered('recurrence', name, c.why);
1438
+ }
1435
1439
  failed('lemmas', verdict);
1436
1440
  proofs.lemmas = { machine: r.machine, laws };
1437
1441
  }
@@ -1472,8 +1476,11 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1472
1476
  const t0 = performance.now(), v = quietly(() => proveRule(r, laws, { search: false })), usualMs = performance.now() - t0, betweenProof = usualProof.last;
1473
1477
  if (cyclicOn())
1474
1478
  crossCyclic('recurrence, between', name, v.proven && v.checked, () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs, betweenProof && translatedRecur({ lemmas: lemmaProof, between: betweenProof }));
1475
- if (certOn() && v.proven && v.checked)
1476
- v.checked = certCyclic('recurrence, between', name, betweenProof && translatedRecur({ lemmas: lemmaProof, between: betweenProof }), () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs).checked;
1479
+ if (certOn() && v.proven && v.checked) {
1480
+ const c = certCyclic('recurrence, between', name, betweenProof && translatedRecur({ lemmas: lemmaProof, between: betweenProof }), () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs);
1481
+ if (!c.checked)
1482
+ uncovered('recurrence, between', name, c.why);
1483
+ }
1477
1484
  failed('laws between two points', v);
1478
1485
  proofs.between = { machine: r.machine, laws };
1479
1486
  verdict = { proven: true, checked: verdict.checked && v.checked };
@@ -1839,10 +1846,11 @@ export class Rule {
1839
1846
  // rank: ({t, served}) => t.plus(1).minus(served), helpful: () => ({op: 1})})
1840
1847
  // batches.leadsTo('every batch is done', {from: () => yes, to: ({waiting, items}) => waiting.eq(0).and(items.eq(0)),
1841
1848
  // rank: ({waiting, items}) => [waiting, items], helpful: [{when: ({items}) => items.gt(0), request: () => ({op: 1})}, {when: ({items}) => items.eq(0), request: () => ({op: 0})}]})
1849
+ // With no rank, proveLeadsTo finds one from the rule's runs (core/cycles.ts's findLeadsTo).
1842
1850
  leadsTo(name, spec) {
1843
1851
  const env = this.env({ flags: false, inputs: false, queries: false });
1844
1852
  const req = (f) => Object.fromEntries(Object.entries(f(env)).map(([n, t]) => [n, termOf(t)]));
1845
- const r = spec.rank(env), rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
1853
+ const r = spec.rank ? spec.rank(env) : [], rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
1846
1854
  const base = { name, from: spec.from(env).c, to: spec.to(env).c, rank };
1847
1855
  return typeof spec.helpful === 'function' ? { ...base, request: req(spec.helpful) } : { ...base, helpful: spec.helpful.map(h => ({ ...(h.when ? { when: h.when(env).c } : {}), request: req(h.request) })) };
1848
1856
  }
@@ -2654,7 +2662,12 @@ function searched(r, laws, stepLaws, v, opts) {
2654
2662
  void _stats;
2655
2663
  return { ...verdict, found: { laws: found, ...(lemmas ? { lemmas } : {}) } };
2656
2664
  }
2657
- export function proveLeadsTo(r, laws, l) {
2665
+ export function proveLeadsTo(r, laws, l, opts = {}) {
2666
+ if (Array.isArray(l.rank) && !l.rank.length) {
2667
+ const f = findLeadsTo(r.machine, withKinds(r, laws), l, { params: (rand, bound) => r.randomParams(rand, bound), input: (rand, bound, ps) => r.randomInput(rand, bound, ps) }, opts);
2668
+ return { proven: f.proven, checked: f.checked, ...(f.goal ? { goal: f.goal, stuck: f.stuck } : {}), ...(f.witness ? { witness: f.witness } : {}), ...(f.why ? { why: f.why } : {}),
2669
+ ...(f.found ? { found: { rank: f.found.rank, ...(f.laws?.length ? { laws: f.laws.map((law, i) => ({ law, text: f.found.laws[i] ?? law.name })) } : {}) } } : {}) };
2670
+ }
2658
2671
  const out = proveLiveness(r.machine, withKinds(r, laws), l);
2659
2672
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2660
2673
  }
@@ -2691,7 +2704,7 @@ export function crossCheckDecision(d, opts = {}) {
2691
2704
  // needs (inferLaws), within `ms` (10 s); the verdict is the kernel's on the rank and the laws found, in `found`.
2692
2705
  export const proveLoop = (l, laws, opts = {}) => {
2693
2706
  if (cyclicOn() || certOn())
2694
- cycLog({ kind: 'loop', name: l.loop.name, uncovered: true, why: 'a claim about runs that never end' });
2707
+ uncovered('loop', l.loop.name, 'a claim about runs that never end');
2695
2708
  if (l.hasRank)
2696
2709
  return goalsVerdict(loopObligations(l.loop, laws, l.rankTerms));
2697
2710
  const out = findRank(l.loop, laws, opts);
@@ -2813,12 +2826,18 @@ export class FoldRule {
2813
2826
  export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
2814
2827
  // A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
2815
2828
  export const proveFold = (f, laws) => {
2816
- // (GDP_CERT=cyclic: the certificate a cyclic proof, core/cyclic.ts's certCyclic, the usual trees its input)
2829
+ // (by default the certificate a cyclic proof, core/cyclic.ts's certCyclic, the usual trees its input; where none is
2830
+ // made, the usual trees checked)
2817
2831
  const all = [...(f.lemmas ?? []), ...laws], t0 = performance.now(), v = goalsVerdict(foldObligations(f.fold, all), !certOn()), usualMs = performance.now() - t0;
2818
2832
  if (cyclicOn())
2819
2833
  crossCyclic('fold', f.fold.name, v.proven && v.checked, () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs, v.trees && (st => translatedProof(() => JSON.stringify(['fold', f.fold, all]), () => foldNodes(f.fold, all, v.trees, st), st)));
2820
- if (certOn() && v.proven)
2821
- v.checked = certCyclic('fold', f.fold.name, v.trees && (st => translatedProof(() => JSON.stringify(['fold', f.fold, all]), () => foldNodes(f.fold, all, v.trees, st), st)), () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs).checked;
2834
+ if (certOn() && v.proven) {
2835
+ const c = certCyclic('fold', f.fold.name, v.trees && (st => translatedProof(() => JSON.stringify(['fold', f.fold, all]), () => foldNodes(f.fold, all, v.trees, st), st)), () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs);
2836
+ if (!c.checked) {
2837
+ uncovered('fold', f.fold.name, c.why);
2838
+ v.checked = foldObligations(f.fold, all).every(g => v.trees?.[g.name] !== undefined && check(g, v.trees[g.name]));
2839
+ }
2840
+ }
2822
2841
  return v;
2823
2842
  };
2824
2843
  const asTerm = (t) => (t instanceof Term ? t : lit(t));
@@ -50,5 +50,8 @@ export type Verdict = {
50
50
  kind: 'unknown';
51
51
  point?: Record<string, number>;
52
52
  };
53
+ export declare const work: {
54
+ n: number;
55
+ };
53
56
  export declare function solve(constraints: Constraint[], maxRows?: number): Verdict;
54
57
  export declare function refuteFast(constraints: Constraint[], maxRows?: number): Verdict | undefined;
@@ -56,7 +56,10 @@ const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
56
56
  const vars = (lin) => Object.keys(lin).filter(key => key !== '1');
57
57
  // maxRows: elimination can square the rows each round; past this many live rows, or a round that would make more than
58
58
  // five times as many before merging them, it gives up with `unknown`.
59
+ // The rows the solver has read and made, which a counted search reads as its clock (kernel/prove.ts's `counted`).
60
+ export const work = { n: 0 };
59
61
  export function solve(constraints, maxRows = 20_000) {
62
+ work.n += constraints.length;
60
63
  const steps = [];
61
64
  const lins = [];
62
65
  const push = (step, lin) => (steps.push(step), lins.push(lin), lins.length - 1);
@@ -173,6 +176,7 @@ export function solve(constraints, maxRows = 20_000) {
173
176
  break;
174
177
  if (choice.pos.length * choice.neg.length > 5 * maxRows)
175
178
  return { kind: 'unknown' };
179
+ work.n += choice.pos.length * choice.neg.length;
176
180
  exact &&= choice.isExact;
177
181
  eliminated.push({ x: choice.x, rows: [...choice.pos, ...choice.neg] });
178
182
  const next = rows.filter(r => (lins[r][choice.x] ?? 0) === 0);
@@ -295,6 +299,7 @@ export function refuteFast(constraints, maxRows = 2_000) {
295
299
  z[j] -= T[r][j];
296
300
  const eps = 1e-9;
297
301
  for (let iter = 0; iter < 50 * (R + m); iter++) {
302
+ work.n += 1 + ((R * cols) >> 8);
298
303
  // Bland's rule: the first column with a negative reduced cost enters (no cycling on degenerate systems).
299
304
  let enter = -1;
300
305
  for (let j = 0; j < cols; j++)
@@ -1,8 +1,9 @@
1
1
  import { add, refuteFast, scale, solve } from "./solver.js";
2
2
  import { checkRefutation } from "./check.js";
3
3
  import { canon, canonFlip, columnTerm, factorsOf, isCell, isMonomial, leafConstraintsReusing, linear, substE, truth, withProducts } from "../kernel/kernel.js";
4
- import { cellProducts, grow } from "../kernel/prove.js";
4
+ import { cellProducts, clock, grow } from "../kernel/prove.js";
5
5
  const omega = 'O$';
6
+ const debugOn = () => !!globalThis.process?.env?.GDP_CYCLIC_DEBUG;
6
7
  // The kernel's product of two linear forms (kernel.ts's `times`): each monomial its factors sorted, joined by '*'.
7
8
  function times(a, b) {
8
9
  const out = [];
@@ -209,6 +210,10 @@ export function translate(part, whole, tree, ren0, st, until, pp = new Map(), wp
209
210
  }
210
211
  if (ok && checkRefutation(withProducts(rw, products, squares), leaf))
211
212
  return { leaf, ...(tree.products ? { products } : {}), ...(tree.squares ? { squares: squares } : {}) };
213
+ if (debugOn()) {
214
+ const missing = tree.leaf.flatMap(s => (s.kind === 'input' && s.index < rp.length && map(s.index) === undefined ? [s.index] : []));
215
+ console.error(`[leaf] ${whole.name}: ${ok ? 'rows found, refutation refused' : `${missing.length} rows missing`}${missing.slice(0, 3).map(i => `; ${JSON.stringify(rp[i].lin)} as ${JSON.stringify(ren(rp[i].lin, wp) ?? 'no form')}${rp[i].src ? ` (${JSON.stringify(rp[i].src)})` : ''}`).join('')}`);
216
+ }
212
217
  return close(whole, wp, st, until);
213
218
  }
214
219
  export function readsFirst(whole, reads, ren, inner, st, until, wp = new Map(), swaps = []) {
@@ -289,7 +294,8 @@ export function decide(whole, conds, inner, st, until, wp = new Map()) {
289
294
  const lin = atoms[0], id = canon(lin);
290
295
  st.splits++;
291
296
  const yes = decide(whole, conds.slice(i), inner, st, until, new Map(wp).set(id, { lin, holds: true }));
292
- const no = yes && regrow(whole, new Map(wp).set(id, { lin, holds: false }), until);
297
+ // (the other side refuted by the node's rows as they are, a time below the steps before it say, before it is grown)
298
+ const np = new Map(wp).set(id, { lin, holds: false }), no = yes && (leafAt(whole, np, true, st) ?? regrow(whole, np, until));
293
299
  return yes && no ? { split: lin, yes, no } : undefined;
294
300
  }
295
301
  return inner(wp);
@@ -439,7 +445,7 @@ export function close(g, wp, st, until) {
439
445
  if (l)
440
446
  return l;
441
447
  const pick = choiceAtom(g, wp) ?? cellAtom(g, wp), om = pick ? undefined : omegaPair(g, wp), next = pick ?? om?.l;
442
- if (next && performance.now() < until) {
448
+ if (next && clock() < until) {
443
449
  st.splits++;
444
450
  const id = canon(next), y = close(g, new Map(wp).set(id, { lin: next, holds: true }), st, until);
445
451
  const no = new Map(wp).set(id, { lin: next, holds: false }), n = y && (om?.q ? apart(g, no, next, om.q, st, until) : close(g, no, st, until));
@@ -509,7 +515,7 @@ export function carry(g, nFacts, nUses, st, until) {
509
515
  };
510
516
  const leafOf = (wp, solveIt) => leafAt(g, wp, solveIt, st), final = (wp) => close(g, wp, st, until);
511
517
  const node = (wp, guardsDone) => {
512
- if (performance.now() > until)
518
+ if (clock() > until)
513
519
  return undefined;
514
520
  // A hypothesis false at the path: the law at t holds there, or a law it is carried from (its guard true) fails.
515
521
  if (truth(law, wp) === true || parts.some(p => (p.guard === undefined || truth(p.guard, wp) === true) && truth(p.concl, wp) === false))
@@ -38,6 +38,9 @@ export type Outcome = {
38
38
  path: Path;
39
39
  why?: string;
40
40
  };
41
+ export declare const clock: () => number;
42
+ export declare function counted<T>(perMs: number, f: () => T): T;
43
+ export declare const withinDeadline: (until: number) => number;
41
44
  export declare function withDeadline<T>(until: number, f: () => T): T;
42
45
  export declare function grow(goal: Goal, path?: Path, budget?: {
43
46
  nodes: number;
@@ -1,6 +1,6 @@
1
- import { add, k, refuteFast, scale, solve, sub, v } from "../core/solver.js";
1
+ import { add, k, refuteFast, scale, solve, sub, v, work } from "../core/solver.js";
2
2
  import { checkRefutation } from "../core/check.js";
3
- import { allowNode, certCyclic, certMs, certOn, certSearch, closingInsts, crossCyclic, cyclicOn, cycLog, hasQuantified, machineNodes, proveCyclic, qflagNodes, reqTS, searchedStepNodes, skMachine, timedKind, withWitness, translatedProof, uncoveredWhy } from "../core/cyclic.js";
3
+ import { allowNode, allowTranslated, certCyclic, certOn, certSearch, closingInsts, crossCyclic, cyclicOn, cycLog, hasQuantified, machineNodes, proveCyclic, reqTS, searchedStepNodes, skMachine, timedKind, withWitness, translatedProof, uncovered, uncoveredWhy } from "../core/cyclic.js";
4
4
  import { assumed, atomOf, boundOf, canon, canonFlip, columnTerm, decided, factorsOf, isCell, isMonomial, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, witnessesOf, withProducts } from "./kernel.js";
5
5
  // The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
6
6
  function openE(e, path, out) {
@@ -193,6 +193,28 @@ export const cellProducts = { on: false };
193
193
  // A deadline every goal grown inside `withDeadline` keeps, beside its own budget: a search that proves many things (core/
194
194
  // infer.ts, core/discover.ts, the search proveRule falls back on) bounds them all; a goal past it is left `unknown`.
195
195
  let deadline = Infinity;
196
+ // The clock every budget here reads (grow's `until`, withDeadline's; core/cyclic.ts's and core/data.ts's searches): wall
197
+ // time, or within `counted` the work the prover has done (the solver's rows read and made, each node's rows), `perMs`
198
+ // units a millisecond, so that what a search's budget lets it try, and so its verdict, doesn't depend on the machine's
199
+ // speed. A counted search inside another reads the outer's clock.
200
+ let rate = 0;
201
+ export const clock = () => (rate ? work.n / rate : performance.now());
202
+ export function counted(perMs, f) {
203
+ if (rate)
204
+ return f();
205
+ const outer = deadline;
206
+ rate = perMs;
207
+ deadline = Infinity;
208
+ try {
209
+ return f();
210
+ }
211
+ finally {
212
+ rate = 0;
213
+ deadline = outer;
214
+ }
215
+ }
216
+ // A budget's end, within the deadline (a translation's own bound, core/cyclic.ts's, kept by the export's GDP_CERT_MS).
217
+ export const withinDeadline = (until) => Math.min(until, deadline);
196
218
  export function withDeadline(until, f) {
197
219
  const outer = deadline;
198
220
  deadline = Math.min(outer, until);
@@ -205,7 +227,7 @@ export function withDeadline(until, f) {
205
227
  }
206
228
  // `units: false`: the goal's own order, no units first (a caller after a model, not a proof: a run guided by the
207
229
  // prover's witnesses, say, whose models units would steer).
208
- export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
230
+ export function grow(goal, path = new Map(), budget = { nodes: 0, until: clock() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
209
231
  if (deadline < (budget.until ?? Infinity))
210
232
  budget.until = deadline;
211
233
  if (budget.units === false && unitsNow) {
@@ -263,11 +285,12 @@ function growPlain(goal, path, budget) {
263
285
  return { stuck: 'unknown', path, why: 'cap' };
264
286
  if (profile.on)
265
287
  profile.nodes++;
266
- if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
288
+ if (++budget.nodes > 200_000 || (budget.until !== undefined && clock() > budget.until))
267
289
  return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
268
290
  // Rows that read a term column only once a cut has put one on the path; before, the rows a choice-free search uses
269
291
  // (what it saw before term columns), renumbered to the whole leaf's when a leaf is found.
270
292
  const full = timed(() => rowsAt(goal, path), ms => { profile.rowsMs += ms; });
293
+ work.n += 1 + full.length;
271
294
  const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
272
295
  const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
273
296
  const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
@@ -1362,16 +1385,14 @@ function proveLawsAsGiven(m, laws) {
1362
1385
  focus: [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])],
1363
1386
  });
1364
1387
  });
1365
- // (GDP_CERT=cyclic: a covered machine's certificate is its cyclic proof, core/cyclic.ts's certCyclic; the usual trees
1366
- // are its input, not checked again)
1388
+ // (by default a covered machine's certificate is its cyclic proof, core/cyclic.ts's certCyclic, the usual trees its
1389
+ // input, not checked again; where none is made, the usual trees are checked and the proof counted uncovered)
1367
1390
  // (what no instance covers, read only where a cyclic mode is on: the usual route's own proofs, inferLaws' trial ones
1368
1391
  // among them, do nothing more than before)
1369
1392
  // A machine with a history: its laws are its plain machine's (historyMachine; the record goals leaf goals beside them).
1370
1393
  const modes = cyclicOn() || certOn(), hist = modes && m.history ? historyMachine(m) : undefined, mc = hist ? hist.machine : m;
1371
- const why = modes ? uncoveredWhy(mc, laws) : undefined;
1372
- // (flags that hold at every key: searched, where GDP_CERT_SEARCH=1)
1373
- const q = why === 'a flag at every key' && certOn() && certSearch(), cert = certOn() && (!why || q);
1374
- usualCheck.on = !cert || q;
1394
+ const why = modes ? uncoveredWhy(mc, laws) : undefined, cert = certOn() && !why;
1395
+ usualCheck.on = !cert;
1375
1396
  const t0 = performance.now(), out = (() => { try {
1376
1397
  return proveThinned(m, laws, { ...instances });
1377
1398
  }
@@ -1381,31 +1402,35 @@ function proveLawsAsGiven(m, laws) {
1381
1402
  if (out.proof)
1382
1403
  usualProof.last = { m, laws, proof: out.proof };
1383
1404
  const inst = out.proof?.instances ?? instances;
1384
- const searched = () => { const { facts, specs } = q ? qflagNodes(mc, laws, inst) : machineNodes(mc, laws, inst); return proveCyclic(facts, specs); };
1385
- const translated = !q && out.proof ? ((st) => translatedProof(() => JSON.stringify(['machine', mc, laws, inst]), () => timedKind('instance', 0, () => machineNodes(mc, laws, inst, out.proof, st)), st)) : undefined;
1405
+ const searched = () => { const { facts, specs } = machineNodes(mc, laws, inst); return proveCyclic(facts, specs); };
1406
+ const translated = out.proof ? ((st) => translatedProof(() => JSON.stringify(['machine', mc, laws, inst]), () => timedKind('instance', 0, () => machineNodes(mc, laws, inst, out.proof, st)), st)) : undefined;
1386
1407
  if (cyclicOn()) {
1387
- if (!why)
1388
- crossCyclic('machine', m.name, out.proven && out.checked, searched, usualMs, translated);
1408
+ const usual = out.proven && out.checked;
1409
+ // (a history: its plain machine's cyclic proof, and its record goals as the leaf goals they are; a machine with flags
1410
+ // that hold at every key the usual route doesn't prove: not searched, as before 0.19.0, a search there costs seconds)
1411
+ const records = !hist || hist.goals.every(g => { const tr = out.proof?.trees[g.name] ?? (() => { const o = grow(g); return 'tree' in o ? o.tree : undefined; })(); return tr !== undefined && check(g, tr); });
1412
+ const recordFails = () => ({ proven: false, checked: false, node: m.name, goal: 'a record goal', stuck: 'not proven' });
1413
+ if (!why && (usual || !hasQuantified(mc)))
1414
+ crossCyclic('machine', m.name, usual, records ? searched : recordFails, usualMs, records ? translated : undefined);
1389
1415
  else
1390
- cycLog({ kind: 'machine', name: m.name, usual: out.proven && out.checked, uncovered: true, why });
1416
+ cycLog({ kind: 'machine', name: m.name, usual, uncovered: true, why: why ?? 'a flag at every key the usual route does not prove' });
1391
1417
  }
1392
1418
  if (cert && out.proven && out.proof) {
1393
- const until = performance.now() + certMs(), c = q ? withDeadline(until, () => certCyclic('machine', m.name, undefined, searched, usualMs)) : certCyclic('machine', m.name, translated, searched, usualMs);
1394
- if (q && !c.checked)
1395
- uncovered('machine', m.name, `${why}: no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`);
1396
- else {
1419
+ const c = certCyclic('machine', m.name, translated, searched, usualMs);
1420
+ if (c.checked) {
1397
1421
  // (a history's record goals checked as they are: leaf goals)
1398
- const records = !hist || hist.goals.every(g => out.proof.trees[g.name] !== undefined && check(g, out.proof.trees[g.name]));
1399
- out.checked = c.checked && records;
1422
+ out.checked = !hist || hist.goals.every(g => out.proof.trees[g.name] !== undefined && check(g, out.proof.trees[g.name]));
1400
1423
  Object.assign(out.proof, { cyclic: c.proof });
1401
1424
  }
1425
+ else {
1426
+ out.checked = checkLaws(m, laws, out.proof);
1427
+ uncovered('machine', m.name, c.why);
1428
+ }
1402
1429
  }
1403
1430
  else if (certOn() && why && out.proven)
1404
1431
  cycLog({ kind: 'machine', name: m.name, cert: true, uncovered: true, why });
1405
1432
  return out;
1406
1433
  }
1407
- // A proof of a kind no cyclic instance covers, counted (`cyclic: {..., uncovered: true}`).
1408
- const uncovered = (kind, name, why) => cycLog({ kind, name, uncovered: true, why });
1409
1434
  // Whether the usual proof's trees are checked (off while GDP_CERT=cyclic makes the certificate a cyclic proof).
1410
1435
  export const usualCheck = { on: true };
1411
1436
  // The last machine proof made (core/dsl.ts's recurrences read their machine's, to translate it: core/cyclic.ts).
@@ -1545,38 +1570,52 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
1545
1570
  leaves += countLeaves(out.tree);
1546
1571
  }
1547
1572
  const proof = { laws: base.proof, instances, ...(Object.keys(perStep).length ? { perStep } : {}), trees };
1548
- const out = { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
1549
1573
  // Step laws of a machine the cyclic instances cover (one step, or several over no map) are translated with the laws
1550
1574
  // (core/cyclic.ts); the rest are counted as no instance's.
1551
- if (!cyclicOn() && !certOn())
1575
+ // (a machine with a history: its plain machine's; a flag at every key through its sk machine; k-step laws over a map
1576
+ // searched, where GDP_CERT_SEARCH=1; where no cyclic proof is made the usual trees, checked above, stay the certificate)
1577
+ const modes = cyclicOn() || certOn(), why = modes ? uncoveredWhy(plain, laws) : undefined, one = stepLaws;
1578
+ // Where the cyclic certificate is made below (GDP_CERT, not the cross-check; the laws' certificate checked), the usual
1579
+ // trees are its input, as a machine's laws' are: checkStepLaws (the laws' usual trees again, then the step laws') runs
1580
+ // only where no certificate is made.
1581
+ const byCert = certOn() && !cyclicOn() && !why && one.length > 0 && base.checked;
1582
+ const out = { proven: true, nodes, leaves, checked: byCert || checkStepLaws(m, laws, stepLaws, proof), proof };
1583
+ if (!modes)
1552
1584
  return out;
1553
- // (a machine with a history: its plain machine's; flags at every key and k-step laws over a map searched, where
1554
- // GDP_CERT_SEARCH=1)
1555
- const why = uncoveredWhy(plain, laws), one = stepLaws.filter(sl => (sl.steps ?? 1) === 1 || (!Object.keys(plain.maps ?? {}).length && !plain.inputMaps?.length));
1556
1585
  if (!why && one.length && out.checked) {
1557
- const steps = one.map(sl => { const inst = perStep[sl.name] ?? instances, goal = stepLawObligations(m, laws, inst, sl)[0]; return { sl, inst, goal, tree: trees[goal.name] }; });
1558
- const translated = (st) => translatedProof(() => JSON.stringify(['steps', plain, laws, base.proof.instances, steps.map(x => [x.sl, x.inst])]), reused => timedKind('instance', 0, () => machineNodes(plain, laws, base.proof.instances, { trees: base.proof.trees, steps, lawsMade: reused }, st)), st, () => JSON.stringify(['machine', plain, laws, base.proof.instances]));
1586
+ const all = one.map(sl => { const inst = perStep[sl.name] ?? instances, goal = stepLawObligations(m, laws, inst, sl)[0]; return { sl, inst, goal, tree: trees[goal.name] }; });
1587
+ const translatedOf = (steps) => (st) => translatedProof(() => JSON.stringify(['steps', plain, laws, base.proof.instances, steps.map(x => [x.sl, x.inst])]), reused => timedKind('instance', 0, () => machineNodes(plain, laws, base.proof.instances, { trees: base.proof.trees, steps, lawsMade: reused }, st)), st, () => JSON.stringify(['machine', plain, laws, base.proof.instances]));
1559
1588
  const usualMs = performance.now() - t0;
1560
1589
  if (cyclicOn())
1561
- crossCyclic('step law', m.name, true, undefined, usualMs, translated);
1562
- if (certOn())
1563
- out.checked = certCyclic('step law', m.name, translated, undefined, usualMs).checked;
1590
+ crossCyclic('step law', m.name, true, undefined, usualMs, translatedOf(all));
1591
+ // (where the step laws' proof together isn't made, each step law's alone: one the translation misses leaves the
1592
+ // others theirs; and the usual trees, unchecked where the certificate was to be the verdict, are checked)
1593
+ const together = certOn() ? certCyclic('step law', m.name, translatedOf(all), undefined, usualMs) : undefined;
1594
+ if (together && !together.checked) {
1595
+ if (byCert)
1596
+ out.checked = checkStepLaws(m, laws, stepLaws, proof);
1597
+ for (const x of all) {
1598
+ const c = all.length > 1 ? certCyclic('step law', m.name, translatedOf([x]), undefined, usualMs) : together;
1599
+ if (!c.checked)
1600
+ uncovered((x.sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, c.why);
1601
+ }
1602
+ }
1564
1603
  }
1565
- const rest = stepLaws.filter(sl => why || !one.includes(sl));
1566
- if (certOn() && certSearch() && out.checked && rest.length && (!why || why === 'a flag at every key')) {
1604
+ // (the rest: k steps over a map a request carries, or a machine no instance covers)
1605
+ const rest = stepLaws.filter(sl => why || !one.includes(sl)), restWhy = (sl) => why ?? `${sl.steps} steps over a map a request carries`;
1606
+ if (certOn() && certSearch() && out.checked && rest.length && !why) {
1567
1607
  // The rest searched: the laws' nodes (searched), each step law's node (core/cyclic.ts's stepLawNodes).
1568
1608
  const searchedSteps = () => {
1569
- const b = why ? qflagNodes(plain, laws, base.proof.instances) : machineNodes(plain, laws, base.proof.instances);
1609
+ const b = machineNodes(plain, laws, base.proof.instances);
1570
1610
  return proveCyclic(b.facts, [...b.specs, ...searchedStepNodes(plain, laws, rest.map(sl => ({ law: sl, instances: perStep[sl.name] ?? instances })), b.facts, b.specs)]);
1571
1611
  };
1572
- const until = performance.now() + certMs(), c = withDeadline(until, () => certCyclic('step law', m.name, undefined, searchedSteps, performance.now() - t0));
1573
- const none = `no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`;
1612
+ const c = certCyclic('step law', m.name, undefined, searchedSteps, performance.now() - t0);
1574
1613
  for (const sl of c.checked ? [] : rest)
1575
- uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, `${why ?? `${sl.steps} steps over a map`}: ${none}`);
1614
+ uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, `${restWhy(sl)}: ${c.why}`);
1576
1615
  }
1577
1616
  else if (cyclicOn() || certOn())
1578
1617
  for (const sl of rest)
1579
- uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, why ?? `${sl.steps} steps over a map`);
1618
+ uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, restWhy(sl));
1580
1619
  return out;
1581
1620
  }
1582
1621
  // Progress (kernel.ts's progressObligations): the laws proven first, then each goal, the every-key laws assumed at every
@@ -1598,20 +1637,26 @@ export function proveProgress(m, laws, p) {
1598
1637
  leaves += countLeaves(out.tree);
1599
1638
  }
1600
1639
  const checked = progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name]));
1601
- // (GDP_CERT=cyclic with GDP_CERT_SEARCH=1: a request allowed whose claim is that alone, over a machine without a history
1602
- // or carried maps, gets core/cyclic.ts's allowNode after the laws' nodes, BridgeRules.lean's cycAOk: from every
1603
- // reachable state, every request meeting the region, its terms and the witnesses withWitness finds is allowed)
1640
+ // (a request allowed whose claim is that alone, over a machine without a history or carried maps: the laws' nodes,
1641
+ // then the allowed goal translated, core/cyclic.ts's allowTranslated, its last node BridgeRules.lean's cycAOk: from
1642
+ // every reachable state, every request meeting the region and its terms is allowed; where GDP_CERT_SEARCH=1 and that
1643
+ // isn't proven, allowNode searched with the witnesses withWitness finds)
1604
1644
  const alone = JSON.stringify(p.then) === JSON.stringify(trueC) && !m.history && !m.inputMaps?.length && m.assume !== undefined;
1605
- if (certOn() && certSearch() && alone && checked && base.proof) {
1606
- const until = performance.now() + certMs(), lp = base.proof, q = hasQuantified(m);
1645
+ if (certOn() && alone && checked && base.proof) {
1646
+ const lp = base.proof, q = hasQuantified(m), goal = progressObligations(m, ls, instances, p)[0];
1647
+ const translated = (st) => translatedProof(() => JSON.stringify(['allowed', m, ls, lp.instances, instances, p]), reused => {
1648
+ const b = machineNodes(m, ls, lp.instances, { ...lp, lawsMade: reused }, st);
1649
+ if (b.why)
1650
+ return b;
1651
+ const a = allowTranslated(m, ls, instances, p, { goal, tree: trees[goal.name] }, b.facts, b.specs, `${m.name}: allowed`, st);
1652
+ return a.why ? { facts: b.facts, specs: [], why: a.why } : { facts: b.facts, specs: [...b.specs, ...a.specs] };
1653
+ }, st, () => JSON.stringify(['machine', m, ls, lp.instances]));
1607
1654
  const allowed = () => {
1608
- const b = q ? qflagNodes(m, ls, lp.instances) : machineNodes(m, ls, lp.instances, lp);
1655
+ const b = machineNodes(m, ls, lp.instances, lp);
1609
1656
  if (b.why || (b.specs[0]?.rank.length ?? 2) < 2)
1610
1657
  return { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: b.why ?? 'laws ranked by time alone' };
1611
1658
  let last = { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: 'no witness' };
1612
1659
  const found = withWitness(m, p.request, wit => {
1613
- if (performance.now() > until)
1614
- return undefined;
1615
1660
  const w = { op: 'and', xs: [p.when, reqTS(Object.entries(p.request)), reqTS(wit)] };
1616
1661
  const spec = allowNode(skMachine(m), ls, instances, w, b.facts, b.specs, `${m.name}: allowed`, q ? closingInsts(m, b.facts, [instances, ...Object.values(lp.instances)], [{ op: 'var', name: 't$' }], ls) : []);
1617
1662
  last = proveCyclic(b.facts, [...b.specs, spec]);
@@ -1619,9 +1664,9 @@ export function proveProgress(m, laws, p) {
1619
1664
  });
1620
1665
  return found?.out ?? last;
1621
1666
  };
1622
- const c = withDeadline(until, () => certCyclic('progress', m.name, undefined, allowed, performance.now() - t0));
1667
+ const c = certCyclic('progress', m.name, translated, certSearch() ? allowed : undefined, performance.now() - t0);
1623
1668
  if (!c.checked)
1624
- uncovered('progress', m.name, `a request allowed: no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`);
1669
+ uncovered('progress', m.name, `a request allowed: ${c.why}`);
1625
1670
  }
1626
1671
  else if (cyclicOn() || certOn())
1627
1672
  uncovered('progress', m.name, alone ? 'a request allowed' : 'a request allowed and a claim after it');