eyeprolog 1.6.6 → 1.6.8

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.
@@ -10,6 +10,7 @@ clause(1,
10
10
  clause(2,
11
11
  xor_row(row(var('X'), var('Y'), var('Z'))),
12
12
  (xor_circuit(var('X'), var('Y'), var('Z')), labeling([var('X'), var('Y'), var('Z')]))).
13
+ clause(3, xor_circuit_verified(var('T')), taut(x # y =:= x * ~ y + ~ x * y, var('T'))).
13
14
 
14
15
  step(xor_row(row(0, 0, 0)),
15
16
  rule(2),
@@ -39,3 +40,5 @@ step(xor_row(row(1, 0, 1)),
39
40
  step(xor_circuit(1, 0, 1), rule(1), ['X' = 1, 'Y' = 0, 'Z' = 1], [sat(1 =:= 1 * ~ 0 + ~ 1 * 0)]).
40
41
  step(sat(1 =:= 1 * ~ 0 + ~ 1 * 0), builtin, [], []).
41
42
  step(labeling([1, 0, 1]), builtin, [], []).
43
+ step(xor_circuit_verified(1), rule(3), ['T' = 1], [taut(x # y =:= x * ~ y + ~ x * y, 1)]).
44
+ step(taut(x # y =:= x * ~ y + ~ x * y, 1), builtin, [], []).
@@ -1,3 +1,43 @@
1
1
  review_quorum(selection(alice(0), bob(0), carol(1), dan(1))).
2
2
  review_quorum(selection(alice(0), bob(1), carol(1), dan(0))).
3
3
  review_quorum_count(2).
4
+
5
+ clause(1,
6
+ review_constraints(var('Alice'), var('Bob'), var('Carol'), var('Dan')),
7
+ sat(card([2], [var('Alice'), var('Bob'), var('Carol'), var('Dan')]) * (var('Alice') =< var('Carol')) * (var('Bob') # var('Dan')))).
8
+ clause(2,
9
+ review_quorum(selection(alice(var('Alice')), bob(var('Bob')), carol(var('Carol')), dan(var('Dan')))),
10
+ (review_constraints(var('Alice'), var('Bob'), var('Carol'), var('Dan')),
11
+ labeling([var('Alice'), var('Bob'), var('Carol'), var('Dan')]))).
12
+ clause(3,
13
+ review_quorum_count(var('Count')),
14
+ sat_count(card([2], [var('Alice'), var('Bob'), var('Carol'), var('Dan')]) * (var('Alice') =< var('Carol')) * (var('Bob') # var('Dan')), var('Count'))).
15
+
16
+ step(review_quorum(selection(alice(0), bob(0), carol(1), dan(1))),
17
+ rule(2),
18
+ ['Alice' = 0, 'Bob' = 0, 'Carol' = 1, 'Dan' = 1],
19
+ [review_constraints(0, 0, 1, 1), labeling([0, 0, 1, 1])]).
20
+ step(review_constraints(0, 0, 1, 1),
21
+ rule(1),
22
+ ['Alice' = 0, 'Bob' = 0, 'Carol' = 1, 'Dan' = 1],
23
+ [sat(card([2], [0, 0, 1, 1]) * (0 =< 1) * (0 # 1))]).
24
+ step(sat(card([2], [0, 0, 1, 1]) * (0 =< 1) * (0 # 1)), builtin, [], []).
25
+ step(labeling([0, 0, 1, 1]), builtin, [], []).
26
+ step(review_quorum(selection(alice(0), bob(1), carol(1), dan(0))),
27
+ rule(2),
28
+ ['Alice' = 0, 'Bob' = 1, 'Carol' = 1, 'Dan' = 0],
29
+ [review_constraints(0, 1, 1, 0), labeling([0, 1, 1, 0])]).
30
+ step(review_constraints(0, 1, 1, 0),
31
+ rule(1),
32
+ ['Alice' = 0, 'Bob' = 1, 'Carol' = 1, 'Dan' = 0],
33
+ [sat(card([2], [0, 1, 1, 0]) * (0 =< 1) * (1 # 0))]).
34
+ step(sat(card([2], [0, 1, 1, 0]) * (0 =< 1) * (1 # 0)), builtin, [], []).
35
+ step(labeling([0, 1, 1, 0]), builtin, [], []).
36
+ step(review_quorum_count(2),
37
+ rule(3),
38
+ ['Count' = 2],
39
+ [sat_count(card([2], [Alice, Bob, Carol, Dan]) * (Alice =< Carol) * (Bob # Dan), 2)]).
40
+ step(sat_count(card([2], [Alice, Bob, Carol, Dan]) * (Alice =< Carol) * (Bob # Dan), 2),
41
+ builtin,
42
+ [],
43
+ []).
@@ -11,6 +11,9 @@ clause(2,
11
11
  feature_plan(features(cloud(var('Cloud')), edge(var('Edge')), audit(var('Audit')), encryption(var('Encryption')))),
12
12
  (feature_constraints(var('Cloud'), var('Edge'), var('Audit'), var('Encryption')),
13
13
  labeling([var('Cloud'), var('Edge'), var('Audit'), var('Encryption')]))).
14
+ clause(3,
15
+ feature_plan_count(var('Count')),
16
+ sat_count((anonymous(1) # var('Edge')) * (var('Audit') =< anonymous(2)) * (var('Edge') =< var('Audit')), var('Count'))).
14
17
 
15
18
  step(feature_plan(features(cloud(0), edge(1), audit(1), encryption(1))),
16
19
  rule(2),
@@ -52,3 +55,8 @@ step(feature_constraints(1, 0, 1, 1),
52
55
  [sat((1 # 0) * (1 =< 1) * (0 =< 1))]).
53
56
  step(sat((1 # 0) * (1 =< 1) * (0 =< 1)), builtin, [], []).
54
57
  step(labeling([1, 0, 1, 1]), builtin, [], []).
58
+ step(feature_plan_count(4),
59
+ rule(3),
60
+ ['Count' = 4],
61
+ [sat_count((_Cloud # Edge) * (Audit =< _Encryption) * (Edge =< Audit), 4)]).
62
+ step(sat_count((_Cloud # Edge) * (Audit =< _Encryption) * (Edge =< Audit), 4), builtin, [], []).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.6.6",
6
+ "version": "1.6.8",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/cli.js CHANGED
@@ -35,7 +35,7 @@ export async function main(argv) {
35
35
  files: [],
36
36
  proof: false,
37
37
  proofDetail: 'abstract',
38
- verifyProof: null,
38
+ checkProof: null,
39
39
  quads: false,
40
40
  quiet: false,
41
41
  stats: false,
@@ -64,10 +64,10 @@ export async function main(argv) {
64
64
  if (detail !== 'abstract' && detail !== 'expanded') throw new Error('--proof-detail requires abstract or expanded');
65
65
  options.proof = true;
66
66
  options.proofDetail = detail;
67
- } else if (!endOptions && arg === '--verify-proof') {
67
+ } else if (!endOptions && arg === '--check-proof') {
68
68
  const file = argv[++i];
69
- if (file == null) throw new Error('--verify-proof requires a file');
70
- options.verifyProof = file;
69
+ if (file == null) throw new Error('--check-proof requires a file');
70
+ options.checkProof = file;
71
71
  } else if (!endOptions && (arg === '--quads' || arg === '-q')) {
72
72
  options.quads = true;
73
73
  } else if (!endOptions && arg === '--quiet') {
@@ -117,18 +117,18 @@ export async function main(argv) {
117
117
  if (options.isoStrict && options.quads) {
118
118
  throw new Error('--iso-strict cannot be combined with --quads');
119
119
  }
120
- if (options.verifyProof != null && options.quads) {
121
- throw new Error('--verify-proof cannot be combined with --quads');
120
+ if (options.checkProof != null && options.quads) {
121
+ throw new Error('--check-proof cannot be combined with --quads');
122
122
  }
123
- if (options.verifyProof != null && options.proof) {
124
- throw new Error('--verify-proof cannot be combined with --proof or --proof-detail');
123
+ if (options.checkProof != null && options.proof) {
124
+ throw new Error('--check-proof cannot be combined with --proof or --proof-detail');
125
125
  }
126
- if (options.verifyProof != null && options.goals.length > 0) {
127
- throw new Error('--verify-proof cannot be combined with --goal');
126
+ if (options.checkProof != null && options.goals.length > 0) {
127
+ throw new Error('--check-proof cannot be combined with --goal');
128
128
  }
129
129
 
130
130
  if (options.isoStrict && options.files.length === 0 && options.goals.length === 0 &&
131
- options.verifyProof == null && !options.proof && !options.quiet && !options.stats && !options.warnings) {
131
+ options.checkProof == null && !options.proof && !options.quiet && !options.stats && !options.warnings) {
132
132
  const engine = await loadEngine();
133
133
  const { runRepl } = await import('./repl.js');
134
134
  const exitCode = await runRepl(engine, {
@@ -172,7 +172,7 @@ export async function main(argv) {
172
172
  sourceParts.push({ text: '', filename: '<empty>' });
173
173
  }
174
174
 
175
- if (options.goals.length === 0 && !options.quads && options.verifyProof == null) {
175
+ if (options.goals.length === 0 && !options.quads && options.checkProof == null) {
176
176
  for (const source of sourceParts) options.goals.push(...goalsFromSource(source.text));
177
177
  }
178
178
 
@@ -188,7 +188,7 @@ export async function main(argv) {
188
188
 
189
189
  const engine = await loadEngine();
190
190
  let program = engine.Program.parseSources(sourceParts, {
191
- sourceMetadata: options.proof || options.verifyProof != null || options.isoStrict,
191
+ sourceMetadata: options.proof || options.checkProof != null || options.isoStrict,
192
192
  isoStrict: options.isoStrict,
193
193
  autoload: options.autoload,
194
194
  autoloadGoals: options.goals,
@@ -198,7 +198,7 @@ export async function main(argv) {
198
198
  // A bare `?- Goal.` asks its question the same way a `%% ?-` comment
199
199
  // does, but only the parser can find it, so it is picked up here rather
200
200
  // than from the source text.
201
- if (options.goals.length === 0 && !options.quads && options.verifyProof == null && program.queries.length > 0) {
201
+ if (options.goals.length === 0 && !options.quads && options.checkProof == null && program.queries.length > 0) {
202
202
  options.goals.push(...program.queries.map((query) => query.goal));
203
203
  program = engine.autoloadProgramGoals(program, options.goals, { autoload: options.autoload });
204
204
  }
@@ -214,16 +214,16 @@ export async function main(argv) {
214
214
  return;
215
215
  }
216
216
 
217
- if (options.verifyProof != null) {
217
+ if (options.checkProof != null) {
218
218
  const { checkProofDocument, verdict } = await import('./check-proof.js');
219
- const proofText = await fs.readFile(options.verifyProof, 'utf8');
219
+ const proofText = await fs.readFile(options.checkProof, 'utf8');
220
220
  const report = checkProofDocument(program, proofText);
221
- if (report.steps === 0) throw new Error(`no step/4 proof step found in ${options.verifyProof}`);
221
+ if (report.steps === 0) throw new Error(`no step/4 proof step found in ${options.checkProof}`);
222
222
  if (!report.valid) {
223
223
  for (const failure of report.failures.slice(0, 5)) {
224
224
  process.stderr.write(` [${failure.condition}] ${failure.conclusion} -- ${failure.detail}\n`);
225
225
  }
226
- throw new Error(`${options.verifyProof} is not a valid proof for this program: ${report.failures.length} failure(s)`);
226
+ throw new Error(`${options.checkProof} is not a valid proof for this program: ${report.failures.length} failure(s)`);
227
227
  }
228
228
  process.stdout.write(`${verdict(report)}.\n`);
229
229
  return;
@@ -317,10 +317,23 @@ async function runDefault(engine, program, options) {
317
317
  });
318
318
  if (options.proof && !options.quiet) {
319
319
  const explanation = await loadExplanation();
320
- const roots = claimed
321
- .map((fact) => explanation.proofNodeFor(program, fact, { registry, proofDetail: options?.proofDetail ?? 'abstract' }))
322
- .filter(Boolean);
320
+ const detail = options?.proofDetail ?? 'abstract';
321
+ const roots = [];
322
+ const unexplained = [];
323
+ for (const fact of claimed) {
324
+ const node = explanation.proofNodeFor(program, fact, { registry, proofDetail: detail, solver });
325
+ if (node) roots.push(node);
326
+ else unexplained.push(fact);
327
+ }
323
328
  const { clauses, steps } = explanation.flattenProof(roots, program);
329
+ // An answer the explanation replay cannot reproduce is recorded as
330
+ // `unproven` rather than left without a step: a document containing
331
+ // one is not a valid proof, and saying so is the point.
332
+ const concluded = new Set(steps.map((step) => engine.termToString(step.conclusion, new engine.Env(), true)));
333
+ for (const fact of unexplained) {
334
+ if (concluded.has(engine.termToString(fact, new engine.Env(), true))) continue;
335
+ steps.push({ conclusion: fact, by: engine.atom('unproven'), bindings: [], uses: [] });
336
+ }
324
337
  process.stdout.write(engine.proofBlocks(program, clauses, steps));
325
338
  }
326
339
  if (haltCode != null) process.exitCode = haltCode;
@@ -347,7 +360,7 @@ Options:
347
360
  -h, --help Show this help text and exit.
348
361
  -p, --proof Enable proof explanations.
349
362
  --proof-detail mode Use abstract or expanded proof detail (implies --proof).
350
- --verify-proof file Check a saved proof document against the input program.
363
+ --check-proof file Check a saved proof document against the input program.
351
364
  -q, --quads Run embedded quad tests and fail if any do not hold.
352
365
  Note: -q is quads, not quiet; --quiet has no short form.
353
366
  --quiet Suppress answer terms while preserving Prolog output.
package/src/explain.js CHANGED
@@ -41,6 +41,19 @@ export function explainProof(program, goal, options = {}) {
41
41
  return whyProof(program, goal, options);
42
42
  }
43
43
 
44
+ // The solver a replay evaluates through.
45
+ //
46
+ // A constraint library keeps state on the solver instance that running a
47
+ // program's directives again does not rebuild -- CLP(B) answers its goals
48
+ // only through the solver that posted the constraints. So when the engine is
49
+ // explaining its own run, it explains through that solver; a caller with none
50
+ // gets a fresh one, which is enough for everything that is pure resolution.
51
+ let liveSolver = null;
52
+
53
+ function hostSolver(program, registry) {
54
+ return liveSolver && liveSolver.program === program ? liveSolver : new Solver(program, { registry });
55
+ }
56
+
44
57
  function* proveGoalAll(program, goal, env, depth, maxDepth, registry, active, detail) {
45
58
  if (depth > maxDepth) return;
46
59
 
@@ -90,7 +103,7 @@ function* proveGoalAll(program, goal, env, depth, maxDepth, registry, active, de
90
103
  // ordinary clauses, but explanations collapse its private helper expansion
91
104
  // behind an explicit library(Name, Arity) boundary.
92
105
  if (detail !== 'expanded' && group.module !== 'user' && program.modules.get(group.module)?.filename?.startsWith('src/lib/')) {
93
- const solver = new Solver(program, { registry });
106
+ const solver = hostSolver(program, registry);
94
107
  for (const next of solver.solve([goal], env.clone(), 0)) {
95
108
  const proofEnv = next.clone ? next.clone() : next;
96
109
  yield {
@@ -199,7 +212,7 @@ function builtinDefinition(program, goal, env, registry) {
199
212
  const def = registry.get(goal.name, goal.arity);
200
213
  if (!def) return { handled: false, def: null, solver: null };
201
214
 
202
- const solver = new Solver(program, { registry });
215
+ const solver = hostSolver(program, registry);
203
216
  if (!builtinIsUsedForGoal(def, solver, goal, env)) return { handled: false, def: null, solver: null };
204
217
  return { handled: true, def, solver };
205
218
  }
@@ -812,10 +825,18 @@ export function flattenProof(roots, program) {
812
825
 
813
826
  // The root of an answer's proof tree, for `flattenProof`.
814
827
  export function proofNodeFor(program, goal, options = {}) {
828
+ liveSolver = options.solver ?? null;
815
829
  const maxDepth = options.maxDepth ?? 256;
816
830
  const registry = options.registry ?? getEyePrologRegistry();
817
831
  const env = options.env ?? new Env();
818
832
  const detail = normalizeProofDetail(options.proofDetail ?? 'abstract');
819
- for (const proof of proveGoalAll(program, goal, env, 0, maxDepth, registry, [], detail)) return proof.node;
833
+ try {
834
+ for (const proof of proveGoalAll(program, goal, env, 0, maxDepth, registry, [], detail)) return proof.node;
835
+ } catch {
836
+ // A replay that raises has not explained anything, and an answer the
837
+ // engine found should not be lost because explaining it failed. The
838
+ // caller records the answer as `unproven`, which says exactly that.
839
+ return null;
840
+ }
820
841
  return null;
821
842
  }
package/src/index.js CHANGED
@@ -40,6 +40,7 @@ import { getStrictIsoRegistry } from './iso.js';
40
40
  import { getEyePrologRegistry } from './standard-library.js';
41
41
  import { executeForwardRules, executeGoals, hasForwardRules, normalizeGoals } from './execute.js';
42
42
  import { proofBlocks } from './result-format.js';
43
+ import { Env, atom, termToString } from './term.js';
43
44
 
44
45
  // The public API is an entry point above the solver/registry layers, so it can
45
46
  // install pruning-aware iterator disposal without introducing an import cycle.
@@ -89,7 +90,7 @@ export function run(source, options = {}) {
89
90
  options.ioOptions?.errorWrite?.(line);
90
91
  },
91
92
  }));
92
- if (includeWhy) output.push(proofBlocksFor(program, derived, runOptions.registry, options.proofDetail));
93
+ if (includeWhy) output.push(proofBlocksFor(program, derived, runOptions.registry, options.proofDetail, solver));
93
94
  } else {
94
95
  // A bare `?- Goal.` asks its question the same way a `%% ?-` comment
95
96
  // does; only the parser can find it, so it is picked up here.
@@ -102,7 +103,7 @@ export function run(source, options = {}) {
102
103
  claimed.push(resolved);
103
104
  },
104
105
  }));
105
- if (includeWhy) output.push(proofBlocksFor(program, claimed, runOptions.registry, options.proofDetail));
106
+ if (includeWhy) output.push(proofBlocksFor(program, claimed, runOptions.registry, options.proofDetail, solver));
106
107
  }
107
108
  return { stdout: output.join(''), stats: solver.stats, haltCode };
108
109
  }
@@ -110,9 +111,25 @@ export function run(source, options = {}) {
110
111
  // The `clause/3` and `step/4` blocks explaining the facts a run claimed.
111
112
  // One walk across every claim, so a conclusion several of them rest on is
112
113
  // explained once.
113
- function proofBlocksFor(program, claimed, registry, proofDetail = 'abstract') {
114
- const roots = claimed.map((fact) => proofNodeFor(program, fact, { registry, proofDetail })).filter(Boolean);
114
+ //
115
+ // An answer the solver found but the explanation replay cannot reproduce --
116
+ // a CLP(B) answer decided by propagation rather than by resolution -- is
117
+ // recorded as `unproven` rather than quietly left without a step. A document
118
+ // containing one is not a valid proof, and saying so is the point.
119
+ function proofBlocksFor(program, claimed, registry, proofDetail = 'abstract', solver = null) {
120
+ const roots = [];
121
+ const unexplained = [];
122
+ for (const fact of claimed) {
123
+ const node = proofNodeFor(program, fact, { registry, proofDetail, solver });
124
+ if (node) roots.push(node);
125
+ else unexplained.push(fact);
126
+ }
115
127
  const { clauses, steps } = flattenProof(roots, program);
128
+ const concluded = new Set(steps.map((step) => termToString(step.conclusion, new Env(), true)));
129
+ for (const fact of unexplained) {
130
+ if (concluded.has(termToString(fact, new Env(), true))) continue;
131
+ steps.push({ conclusion: fact, by: atom('unproven'), bindings: [], uses: [] });
132
+ }
116
133
  return proofBlocks(program, clauses, steps);
117
134
  }
118
135
 
@@ -4338,7 +4338,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
4338
4338
  },
4339
4339
  },
4340
4340
  {
4341
- name: '--verify-proof re-performs a saved proof and rejects tampering',
4341
+ name: '--check-proof re-performs a saved proof and rejects tampering',
4342
4342
  run: () => {
4343
4343
  const programFile = path.join(temp.dir, `proof-program-${++temp.counter}.pl`);
4344
4344
  const proofFile = path.join(temp.dir, `proof-certificate-${++temp.counter}.pl`);
@@ -4346,15 +4346,15 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
4346
4346
  const generated = runCli(['--proof', programFile]);
4347
4347
  assertEqual(generated.status, 0, 'proof generation status');
4348
4348
  fs.writeFileSync(proofFile, generated.stdout);
4349
- const verified = runCli(['--verify-proof', proofFile, programFile]);
4349
+ const verified = runCli(['--check-proof', proofFile, programFile]);
4350
4350
  assertEqual(verified.status, 0, 'verification status');
4351
4351
  assertEqual(verified.stdout, 'checked: 2 steps.\n', 'verification stdout');
4352
- const strictVerified = runCli(['--iso-strict', '--verify-proof', proofFile, programFile]);
4352
+ const strictVerified = runCli(['--iso-strict', '--check-proof', proofFile, programFile]);
4353
4353
  assertEqual(strictVerified.status, 0, 'strict verification status');
4354
4354
  assertEqual(strictVerified.stdout, 'checked: 2 steps.\n', 'strict verification stdout');
4355
4355
  const tamperedFile = path.join(temp.dir, `proof-certificate-bad-${++temp.counter}.pl`);
4356
4356
  fs.writeFileSync(tamperedFile, generated.stdout.replace('step(p(a),', 'step(p(b),'));
4357
- const rejected = runCli(['--verify-proof', tamperedFile, programFile]);
4357
+ const rejected = runCli(['--check-proof', tamperedFile, programFile]);
4358
4358
  assertEqual(rejected.status, 1, 'tampered verification status');
4359
4359
  assertIncludes(rejected.stderr, 'is not a valid proof for this program', 'tampered verification stderr');
4360
4360
  },
@@ -19,22 +19,37 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
19
  const examplesDir = path.join(root, 'examples');
20
20
 
21
21
  /// Proofs that do not check, and what each one exposes. An entry here is a
22
- /// gap in proof *generation*, not in the checker: these answers come from
23
- /// CLP(B) propagation, which the explanation replay cannot reproduce by
24
- /// resolution, so the writer records them as `unproven` and says so rather
25
- /// than leaving them out.
26
- const KNOWN_GAPS = new Map([
27
- ['clpb-boolean-circuit.pl', 'a CLP(B) answer decided by propagation, not by resolution'],
28
- ['clpb-cardinality.pl', 'a CLP(B) answer decided by propagation, not by resolution'],
29
- ['clpb-feature-model.pl', 'a CLP(B) answer decided by propagation, not by resolution'],
30
- ]);
22
+ /// gap in proof *generation*, not in the checker: the explanation replay
23
+ /// cannot reproduce the answer, so the writer records it as `unproven` and
24
+ /// says so rather than leaving it out.
25
+ ///
26
+ /// Explaining through the solver that produced the answer closed the three
27
+ /// CLP(B) gaps that used to be here, so the list is empty: every packaged
28
+ /// proof checks, and this test fails if one stops doing so.
29
+ ///
30
+ /// The CLP(Z) examples are not in this corpus, and not because they fail:
31
+ /// `clpz`'s goal expansion names the variables it introduces from a counter
32
+ /// that advances across a run, so a `clause/3` record of an expanded body
33
+ /// differs between runs and cannot be compared against a golden. Checked
34
+ /// directly rather than through a golden, `clpz-factorial`, `clpz-n-queens`,
35
+ /// `clpz-global-constraints` and `clpz-sudoku-9x9` all check.
36
+ const KNOWN_GAPS = new Map([]);
37
+
38
+ const totals = { steps: 0, verified: 0, trusted: 0 };
31
39
 
32
40
  export function runProofChecking(reporter = new TestReporter()) {
33
41
  reporter.section('Proof checking');
42
+ totals.steps = 0;
43
+ totals.verified = 0;
44
+ totals.trusted = 0;
34
45
  for (const name of [...proofExamples].sort()) {
35
46
  reporter.test(name, () => checkPackagedProof(name));
36
47
  }
37
48
  reporter.sectionTotal('proof checking');
49
+ // What the corpus establishes, in the terms the specification uses: a
50
+ // verified step was re-performed, a trusted one was recorded because
51
+ // re-deciding it would mean running the program.
52
+ reporter.section(`${totals.steps} steps, ${totals.verified} verified, ${totals.trusted} trusted`);
38
53
  }
39
54
 
40
55
  function checkPackagedProof(name) {
@@ -42,6 +57,9 @@ function checkPackagedProof(name) {
42
57
  const proof = fs.readFileSync(path.join(examplesDir, 'proof', name), 'utf8');
43
58
  const program = Program.parseSources([{ text: source, filename: name }], { sourceMetadata: true });
44
59
  const report = checkProofDocument(program, proof);
60
+ totals.steps += report.steps;
61
+ totals.verified += report.verified;
62
+ totals.trusted += report.trusted.length;
45
63
  const gap = KNOWN_GAPS.get(name);
46
64
  if (gap) {
47
65
  assertEqual(report.valid, false, `${name} now checks; remove it from KNOWN_GAPS (${gap})`);
@@ -1494,7 +1494,7 @@ output is valid EyeProlog input and can be kept and checked later:
1494
1494
 
1495
1495
  ```sh
1496
1496
  eyeprolog --proof examples/socrates.pl > socrates.why.pl
1497
- eyeprolog --verify-proof socrates.why.pl examples/socrates.pl
1497
+ eyeprolog --check-proof socrates.why.pl examples/socrates.pl
1498
1498
  ```
1499
1499
 
1500
1500
  The second command re-performs every inference the document records against
@@ -9589,7 +9589,7 @@ explicit.
9589
9589
  | `-h`, `--help` | Show usage |
9590
9590
  | `-p`, `--proof` | Print `why/2` explanations |
9591
9591
  | `--proof-detail abstract|expanded` | Select library abstraction for proof output; implies `--proof` |
9592
- | `--verify-proof File` | Verify saved `why/2` proof certificates against the input program without proof search |
9592
+ | `--check-proof File` | Verify saved `why/2` proof certificates against the input program without proof search |
9593
9593
  | `-q`, `--quads` | Run embedded quad tests and fail if any do not hold |
9594
9594
  | `--quiet` | Suppress resolved answer terms while preserving Prolog output and diagnostics |
9595
9595
  | `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions (including `table` and `:+`) and disable bundled-library autoloading |
@@ -9636,7 +9636,7 @@ Work in a fixed sequence:
9636
9636
  1. predict the ground answers before running the program;
9637
9637
  2. run without observation flags and compare stdout with that prediction;
9638
9638
  3. add `--proof` when the support for an answer is the question; save the
9639
- output and use `--verify-proof` when the derivation itself must cross a
9639
+ output and use `--check-proof` when the derivation itself must cross a
9640
9640
  process or review boundary;
9641
9641
  4. add `--warnings` when portability or negative dependencies are the
9642
9642
  question; use `--portable` when non-profile dependencies must fail CI;
@@ -9648,7 +9648,7 @@ For example:
9648
9648
  eyeprolog --goal 'ancestor(X, Y)' examples/ancestor.pl
9649
9649
  eyeprolog --proof --goal 'type(X, Y)' examples/socrates.pl
9650
9650
  eyeprolog --proof examples/socrates.pl > socrates.why.pl
9651
- eyeprolog --verify-proof socrates.why.pl examples/socrates.pl
9651
+ eyeprolog --check-proof socrates.why.pl examples/socrates.pl
9652
9652
  eyeprolog --warnings --goal 'answer(X)' test/conformance/warnings/negation/unstratified_mutual.pl
9653
9653
  eyeprolog --portable --goal 'sudoku9(S)' examples/clpz-sudoku-9x9.pl
9654
9654
  eyeprolog --stats --goal 'path(a, X)' examples/path-discovery.pl > answers.pl 2> run.stats