eyeprolog 1.6.12 → 1.6.13

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.
Files changed (129) hide show
  1. package/examples/proof/abstract-interpretation.pl +146 -0
  2. package/examples/proof/ackermann.pl +296 -0
  3. package/examples/proof/alignment-demo.pl +196 -0
  4. package/examples/proof/allen-interval-calculus.pl +1271 -0
  5. package/examples/proof/auroracare.pl +954 -0
  6. package/examples/proof/basic-monadic.pl +37176 -0
  7. package/examples/proof/bayes-therapy.pl +1349 -0
  8. package/examples/proof/bench.pl +2802 -0
  9. package/examples/proof/binomial-vandermonde.pl +983 -0
  10. package/examples/proof/blocks-world-planning.pl +471 -0
  11. package/examples/proof/bmi.pl +386 -0
  12. package/examples/proof/braking-safety-worlds.pl +508 -0
  13. package/examples/proof/buck-converter-design.pl +179 -0
  14. package/examples/proof/bulk-stream-write.pl +3 -0
  15. package/examples/proof/collatz-1000.pl +43784 -0
  16. package/examples/proof/competitive-enzyme-kinetics.pl +167 -0
  17. package/examples/proof/complex.pl +514 -0
  18. package/examples/proof/context-schema-audit.pl +374 -0
  19. package/examples/proof/continued-fraction-sqrt2.pl +476 -0
  20. package/examples/proof/control-system.pl +137 -0
  21. package/examples/proof/critical-path-schedule.pl +491 -0
  22. package/examples/proof/cross-organization-data-sharing.pl +732 -0
  23. package/examples/proof/cyclic-path.pl +44 -0
  24. package/examples/proof/dairy-energy-balance.pl +290 -0
  25. package/examples/proof/dcg-expression-language.pl +157 -0
  26. package/examples/proof/deep-taxonomy-10.pl +185 -0
  27. package/examples/proof/deep-taxonomy-100.pl +365 -0
  28. package/examples/proof/deep-taxonomy-1000.pl +163 -0
  29. package/examples/proof/deep-taxonomy-10000.pl +163 -0
  30. package/examples/proof/deep-taxonomy-100000.pl +163 -0
  31. package/examples/proof/delfour.pl +1615 -0
  32. package/examples/proof/dijkstra-findall-sort.pl +354 -0
  33. package/examples/proof/dijkstra-risk-path.pl +820 -0
  34. package/examples/proof/dijkstra.pl +753 -0
  35. package/examples/proof/dining-philosophers.pl +2663 -0
  36. package/examples/proof/donald-gerald-robert.pl +311 -0
  37. package/examples/proof/dpv-odrl-purpose-mapping.pl +1189 -0
  38. package/examples/proof/drone-corridor-planner.pl +2365 -0
  39. package/examples/proof/easter-computus.pl +1406 -0
  40. package/examples/proof/eulerian-path.pl +457 -0
  41. package/examples/proof/ev-range-worlds.pl +508 -0
  42. package/examples/proof/exoplanet-validation-worlds.pl +397 -0
  43. package/examples/proof/explainable-ev-depot-configuration.pl +313 -0
  44. package/examples/proof/family-cousins.pl +920 -0
  45. package/examples/proof/fast-fourier-transform.pl +5045 -0
  46. package/examples/proof/fastpow.pl +769 -0
  47. package/examples/proof/fft8-numeric.pl +685 -0
  48. package/examples/proof/fibonacci.pl +1425 -0
  49. package/examples/proof/field-nitrogen-balance.pl +320 -0
  50. package/examples/proof/flandor.pl +533 -0
  51. package/examples/proof/fundamental-theorem-arithmetic.pl +245 -0
  52. package/examples/proof/gd-step-certified.pl +1717 -0
  53. package/examples/proof/goldbach.pl +1135 -0
  54. package/examples/proof/gps.pl +627 -0
  55. package/examples/proof/graph.pl +105 -0
  56. package/examples/proof/gray-code-counter.pl +355 -0
  57. package/examples/proof/hamiltonian-path.pl +3373 -0
  58. package/examples/proof/hamming-code.pl +232 -0
  59. package/examples/proof/heron-theorem.pl +79 -0
  60. package/examples/proof/http-client.pl +0 -0
  61. package/examples/proof/illegitimate-reasoning.pl +220 -0
  62. package/examples/proof/integer-partitions.pl +1642 -0
  63. package/examples/proof/iso-atomic-conversion.pl +73 -0
  64. package/examples/proof/knowledge-engineering-alignment-flow.pl +200 -0
  65. package/examples/proof/knuth-bendix-completion.pl +290 -0
  66. package/examples/proof/law-of-cosines.pl +71 -0
  67. package/examples/proof/least-squares-regression.pl +342 -0
  68. package/examples/proof/lee.pl +637 -0
  69. package/examples/proof/lldm.pl +581 -0
  70. package/examples/proof/manufacturing-quality-control.pl +181 -0
  71. package/examples/proof/map-four-color-search.pl +796 -0
  72. package/examples/proof/markov-logic-network.pl +405 -0
  73. package/examples/proof/matrix-chain-order.pl +164 -0
  74. package/examples/proof/matrix-noncommutativity.pl +183 -0
  75. package/examples/proof/microgrid-dispatch.pl +164 -0
  76. package/examples/proof/missionaries-cannibals.pl +428 -0
  77. package/examples/proof/modular-exponentiation.pl +838 -0
  78. package/examples/proof/monkey-bananas.pl +278 -0
  79. package/examples/proof/network-sla.pl +100 -0
  80. package/examples/proof/newton-raphson.pl +520 -0
  81. package/examples/proof/observability-log-correlation.pl +1061 -0
  82. package/examples/proof/odrl-dpv-fpv-trust-flow.pl +1366 -0
  83. package/examples/proof/odrl-dpv-healthcare-risk-ranked.pl +488 -0
  84. package/examples/proof/odrl-dpv-risk-ranked.pl +637 -0
  85. package/examples/proof/odrl-policy-advanced.pl +705 -0
  86. package/examples/proof/odrl-policy-reasoning.pl +1423 -0
  87. package/examples/proof/odrl-policy.pl +103 -0
  88. package/examples/proof/operational-incident-response.pl +435 -0
  89. package/examples/proof/orbital-transfer-design.pl +288 -0
  90. package/examples/proof/path-discovery.pl +502 -0
  91. package/examples/proof/peano-arithmetic.pl +910 -0
  92. package/examples/proof/peano-calculus.pl +294 -0
  93. package/examples/proof/pell-equation.pl +295 -0
  94. package/examples/proof/pendulum-period.pl +97 -0
  95. package/examples/proof/pi.pl +3 -0
  96. package/examples/proof/pointer-analysis.pl +94 -0
  97. package/examples/proof/polynomial.pl +2487 -0
  98. package/examples/proof/portable-library-overlap.pl +6 -0
  99. package/examples/proof/quadratic-formula.pl +159 -0
  100. package/examples/proof/quine-mccluskey.pl +914 -0
  101. package/examples/proof/radioactive-decay.pl +104 -0
  102. package/examples/proof/rdf12-annotated-claims.pl +263 -0
  103. package/examples/proof/register-allocation.pl +120 -0
  104. package/examples/proof/relational-cube-lookup.pl +15867 -0
  105. package/examples/proof/riemann-hypothesis.pl +148 -0
  106. package/examples/proof/sat-solver-dpll.pl +226 -0
  107. package/examples/proof/scientific-evidence-graph.pl +681 -0
  108. package/examples/proof/send-more-money.pl +252 -0
  109. package/examples/proof/service-impact.pl +151 -0
  110. package/examples/proof/shoelace-polygon-area.pl +161 -0
  111. package/examples/proof/skolem-functions.pl +128 -0
  112. package/examples/proof/statistics-summary.pl +250 -0
  113. package/examples/proof/stirling-bell-numbers.pl +125 -0
  114. package/examples/proof/superdense-coding.pl +118 -0
  115. package/examples/proof/symbiotic-knowledge-graph.pl +1569 -0
  116. package/examples/proof/truth-maintenance-system.pl +466 -0
  117. package/examples/proof/turing.pl +1183 -0
  118. package/examples/proof/type-inference.pl +267 -0
  119. package/examples/proof/vector-similarity.pl +163 -0
  120. package/examples/proof/vulnerability-impact.pl +180 -0
  121. package/examples/proof/wolf-goat-cabbage.pl +339 -0
  122. package/examples/proof/zebra.pl +176 -0
  123. package/package.json +1 -1
  124. package/src/check-proof.js +159 -11
  125. package/test/conformance/NEUMERKEL-LATEST.md +3 -3
  126. package/test/regression/cases-white-box.mjs +38 -0
  127. package/test/run-examples.mjs +7 -111
  128. package/test/run-proof-checking.mjs +28 -5
  129. package/the-art-of-eyeprolog.md +4 -2
@@ -15,20 +15,86 @@
15
15
  // C3 Justification -- every step carries exactly one known justification.
16
16
  // C4 Coverage -- every claim has a step, and every use resolves to
17
17
  // a step or to a statement the program gives.
18
+ // C5 Re-decision -- a step the document only asserts, rather than
19
+ // deriving, is computed again here and must agree.
18
20
  //
19
- // `builtin`, `absent` and `collected` steps are *trusted*, not checked:
20
- // re-deciding them would mean running the program, which is what a checker
21
- // must not do. They are reported so a reader knows what the check rests on.
21
+ // C5 is what keeps this a check rather than a reading. A condition that
22
+ // cannot fail is not much of a check, so a step whose justification is
23
+ // `builtin` -- the largest class in a typical proof -- is not taken on the
24
+ // document's word. Its goal is run again against a program holding the
25
+ // bundled libraries and nothing else: no clause of the theory under proof is
26
+ // present, so the re-decision cannot be talked into agreeing by the very
27
+ // rules it is meant to audit. Disagreement is a C5 failure.
28
+ //
29
+ // This does not make the checker reason. It still never searches for a
30
+ // derivation the document failed to record; it only recomputes primitives
31
+ // the document asserts without one.
32
+ //
33
+ // `absent`, `collected` and `asserted` stay trusted, and deliberately.
34
+ // `absent` is negation as failure over the theory, so re-deciding it would
35
+ // mean searching exactly the clauses C5 excludes -- and doing it in the
36
+ // library-only program would make every such step pass for the wrong reason,
37
+ // which is weaker than admitting it was trusted. They are reported so a
38
+ // reader knows what the check still rests on.
22
39
  import { ATOM, COMPOUND, Env, compareTerms, copyResolved, freshTerm, properListItems, termToString, unify } from './term.js';
23
40
  import { parseProgramText } from './parser.js';
24
41
  import { clauseNumbering } from './explain.js';
42
+ import { Program, autoloadProgramGoals } from './program.js';
43
+ import { Solver } from './solver.js';
25
44
 
26
45
  const CHECKED = new Set(['rule', 'fact']);
27
- // Recorded rather than re-derived: deciding one again would mean running
28
- // the program, which is what a checker must not do. `asserted` is
29
- // eyeprolog's own: a clause `assert/1` created at run time is in no source
30
- // file, so there is nothing to check it against.
31
- const TRUSTED = new Set(['builtin', 'absent', 'collected', 'asserted']);
46
+ // Recomputed independently by C5 rather than taken on the document's word.
47
+ const REDECIDED = new Set(['builtin']);
48
+
49
+ // Goals that state a fact about the run rather than compute a value, and so
50
+ // cannot be recomputed by a program that deliberately holds neither the
51
+ // theory nor the run's accumulated state. Re-running one of these against
52
+ // C5's library-only program would decide it on the wrong evidence, which is
53
+ // weaker than admitting it was trusted -- so they are named here, by the two
54
+ // reasons they are outside C5's reach, and reported as obligations.
55
+ //
56
+ // Reflective: the goal reads the theory's own database or syntax, which is
57
+ // exactly what C5 excludes in order to stay independent of it.
58
+ // Stateful: the goal's answer depends on constraint or attribute state that
59
+ // the original run built up and a fresh solver has no way to reconstruct.
60
+ const NOT_REDECIDABLE = new Map([
61
+ ['clause/2', 'reflective'],
62
+ ['current_predicate/1', 'reflective'],
63
+ ['current_op/3', 'reflective'],
64
+ ['get_atts/2', 'stateful'],
65
+ ['put_atts/2', 'stateful'],
66
+ // Stream operations name a handle the original run opened, and re-running
67
+ // one would not merely fail -- it would perform I/O of its own. A checker
68
+ // must not have side effects, so these are never recomputed.
69
+ ['open/3', 'stateful'],
70
+ ['open/4', 'stateful'],
71
+ ['close/1', 'stateful'],
72
+ ['close/2', 'stateful'],
73
+ ['read/2', 'stateful'],
74
+ ['read_term/3', 'stateful'],
75
+ ['write/2', 'stateful'],
76
+ ['write_term/3', 'stateful'],
77
+ ['nl/1', 'stateful'],
78
+ ['set_output/1', 'stateful'],
79
+ ['set_input/1', 'stateful'],
80
+ ['current_output/1', 'stateful'],
81
+ ['current_input/1', 'stateful'],
82
+ ['at_end_of_stream/1', 'stateful'],
83
+ ['stream_property/2', 'stateful'],
84
+ ['sat/1', 'stateful'],
85
+ ['taut/2', 'stateful'],
86
+ ['sat_count/2', 'stateful'],
87
+ ]);
88
+
89
+ function notRedecidableReason(goal) {
90
+ if (goal?.type !== COMPOUND && goal?.type !== ATOM) return null;
91
+ return NOT_REDECIDABLE.get(`${goal.name}/${goal.arity ?? 0}`) ?? null;
92
+ }
93
+ // Recorded rather than re-derived. `absent` and `collected` range over the
94
+ // theory under proof, which C5's program deliberately excludes. `asserted`
95
+ // is eyeprolog's own: a clause `assert/1` created at run time is in no
96
+ // source file, so there is nothing to check it against.
97
+ const TRUSTED = new Set(['absent', 'collected', 'asserted']);
32
98
 
33
99
  function key(term) {
34
100
  return termToString(term, new Env(), true);
@@ -79,6 +145,7 @@ export function readProofDocument(text, program) {
79
145
  // clause it cites. A step that carries none, or more than one, fails C3.
80
146
  function justificationOf(step) {
81
147
  const by = step.by;
148
+ if (by?.type === ATOM && REDECIDED.has(by.name)) return { kind: by.name, clause: null };
82
149
  if (by?.type === ATOM && TRUSTED.has(by.name)) return { kind: by.name, clause: null };
83
150
  if (by?.type === COMPOUND && CHECKED.has(by.name) && by.arity === 1) {
84
151
  const number = Number(by.args[0]?.name);
@@ -149,6 +216,59 @@ function checkWellFounded(byConclusion, failures) {
149
216
  }
150
217
  }
151
218
 
219
+ // C5's independent route. One program is built per document and reused: it
220
+ // holds the bundled libraries the recorded goals need and no clause of the
221
+ // theory under proof, so a goal that only the theory could satisfy raises an
222
+ // existence error here rather than quietly succeeding.
223
+ //
224
+ // A step is only re-decided when this program can actually run its goal.
225
+ // When it cannot -- the predicate is not one the libraries define -- the step
226
+ // is reported as not re-decidable instead of being failed: the checker has
227
+ // learned nothing about it, and saying so is honest where failing it would
228
+ // not be.
229
+ function makeRedecider(program, steps) {
230
+ const goals = [];
231
+ for (const step of steps) {
232
+ const justification = justificationOf(step);
233
+ if (justification && REDECIDED.has(justification.kind)) goals.push(step.conclusion);
234
+ }
235
+ if (goals.length === 0) return () => ({ status: 'unavailable' });
236
+
237
+ let primitives = null;
238
+ try {
239
+ primitives = Program.parseSources([{ text: '', filename: '<check>' }], {
240
+ sourceMetadata: false,
241
+ isoStrict: program?.strictIso === true,
242
+ });
243
+ primitives = autoloadProgramGoals(primitives, goals, { autoload: true });
244
+ } catch (_) {
245
+ // No library program could be prepared, so nothing can be re-decided.
246
+ return () => ({ status: 'unavailable' });
247
+ }
248
+
249
+ return (goal) => {
250
+ let solver;
251
+ try {
252
+ solver = new Solver(primitives, { solutionLimit: 1 });
253
+ } catch (_) {
254
+ return { status: 'unavailable' };
255
+ }
256
+ try {
257
+ const result = solver.runOneAnswer(goal, new Env());
258
+ return result.done ? { status: 'disagrees' } : { status: 'agrees' };
259
+ } catch (error) {
260
+ // An existence error means the libraries do not define this predicate,
261
+ // so the step names something outside what C5 can recompute. Any other
262
+ // error is the goal genuinely going wrong, which is a disagreement.
263
+ const formal = error?.formal ?? '';
264
+ if (typeof formal === 'string' && formal.startsWith('existence_error(procedure')) {
265
+ return { status: 'unavailable' };
266
+ }
267
+ return { status: 'disagrees', detail: error?.message ?? String(error) };
268
+ }
269
+ };
270
+ }
271
+
152
272
  let checkFreshCounter = 0;
153
273
 
154
274
  export function checkProofDocument(program, text) {
@@ -157,6 +277,8 @@ export function checkProofDocument(program, text) {
157
277
  const failures = [];
158
278
  const trusted = [];
159
279
  let verified = 0;
280
+ let redecided = 0;
281
+ const redecide = makeRedecider(program, steps);
160
282
 
161
283
  const byConclusion = new Map();
162
284
  for (const step of steps) {
@@ -182,6 +304,29 @@ export function checkProofDocument(program, text) {
182
304
  failures.push({ condition: 'C3', conclusion: key(step.conclusion), detail: `unknown justification ${key(step.by)}` });
183
305
  continue;
184
306
  }
307
+ if (REDECIDED.has(justification.kind)) {
308
+ const reason = notRedecidableReason(step.conclusion);
309
+ if (reason) {
310
+ trusted.push({ kind: justification.kind, conclusion: key(step.conclusion), reason });
311
+ continue;
312
+ }
313
+ const outcome = redecide(step.conclusion);
314
+ if (outcome.status === 'agrees') {
315
+ redecided++;
316
+ } else if (outcome.status === 'disagrees') {
317
+ failures.push({
318
+ condition: 'C5',
319
+ conclusion: key(step.conclusion),
320
+ detail: outcome.detail
321
+ ? `recomputing this ${justification.kind} step went wrong: ${outcome.detail}`
322
+ : `recomputing this ${justification.kind} step does not give it`,
323
+ });
324
+ } else {
325
+ // Nothing was learned about this step, so it is still an obligation.
326
+ trusted.push({ kind: justification.kind, conclusion: key(step.conclusion) });
327
+ }
328
+ continue;
329
+ }
185
330
  if (TRUSTED.has(justification.kind)) {
186
331
  trusted.push({ kind: justification.kind, conclusion: key(step.conclusion) });
187
332
  continue;
@@ -215,7 +360,7 @@ export function checkProofDocument(program, text) {
215
360
 
216
361
  checkWellFounded(byConclusion, failures);
217
362
 
218
- return { valid: failures.length === 0, steps: steps.length, verified, trusted, failures, claims: claims.length };
363
+ return { valid: failures.length === 0, steps: steps.length, verified, redecided, trusted, failures, claims: claims.length };
219
364
  }
220
365
 
221
366
  // The program's clauses, numbered from 1 in load order -- the numbering
@@ -229,6 +374,9 @@ function programClauses(program) {
229
374
 
230
375
  export function verdict(report) {
231
376
  if (!report.valid) return `${report.failures.length} failure(s)`;
232
- if (report.trusted.length) return `checked with obligations: ${report.steps} steps, ${report.trusted.length} trusted`;
233
- return `checked: ${report.steps} steps`;
377
+ const recomputed = report.redecided ? `, ${report.redecided} recomputed` : '';
378
+ if (report.trusted.length) {
379
+ return `checked with obligations: ${report.steps} steps${recomputed}, ${report.trusted.length} trusted`;
380
+ }
381
+ return `checked: ${report.steps} steps${recomputed}`;
234
382
  }
@@ -1,6 +1,6 @@
1
1
  # EyeProlog — latest Neumerkel conformity
2
2
 
3
- Status: **PASS** — **759/759** discovered upstream cases passed.
3
+ Status: **PASS** — **766/766** discovered upstream cases passed.
4
4
 
5
5
  This tracked report records the latest upstream inventory successfully checked by EyeProlog.
6
6
  `npm test` fetches the eight TU Wien sources again and executes the discovered cases.
@@ -10,14 +10,14 @@ Counts are output from upstream, not hard-coded test constants.
10
10
  | Suite | Passed | Total |
11
11
  |---|---:|---:|
12
12
  | syntax | 380 | 380 |
13
- | number_chars/2 | 86 | 86 |
13
+ | number_chars/2 | 93 | 93 |
14
14
  | variable_names/1 | 75 | 75 |
15
15
  | dif/2 | 26 | 26 |
16
16
  | length/2 | 37 | 37 |
17
17
  | phrase/2,3 | 58 | 58 |
18
18
  | Prologue draft | 72 | 72 |
19
19
  | setup_call_cleanup/3 | 25 | 25 |
20
- | **Total** | **759** | **759** |
20
+ | **Total** | **766** | **766** |
21
21
 
22
22
  ## Upstream sources
23
23
 
@@ -7,6 +7,7 @@ import * as publicApi from '../../src/index.js';
7
7
  import { Env, Program, atom, compound, copyResolved, flattenConjunction, listFromItems, numberTerm, parseProgramText, properListItems, stringTerm, termIsGround, termToString, unify, variable, variantTerms } from '../../src/index.js';
8
8
  import { ISO_OPERATOR_DEFINITIONS, parseGoalText, parseNumberTokenText } from '../../src/parser.js';
9
9
  import { compareTerms } from '../../src/term.js';
10
+ import { checkProofDocument, verdict } from '../../src/check-proof.js';
10
11
  import { formatTermForWrite } from '../../src/write.js';
11
12
  import { selectClauseCandidates } from '../../src/program.js';
12
13
  import { assertEqual, assertIncludes, assertNotIncludes } from '../test-style.mjs';
@@ -20,6 +21,43 @@ import {
20
21
 
21
22
  export function whiteBoxCases() {
22
23
  return [
24
+ {
25
+ // The point of C5. A proof can be internally flawless -- every step an
26
+ // instance of the clause it cites, every use accounted for, no cycles --
27
+ // and still record a computed value that is simply false. Conditions
28
+ // C1-C4 read the document; only C5 recomputes what it asserts, so only
29
+ // C5 can disagree with it. If this case ever passes the checker, the
30
+ // strongest thing proof checking claims has quietly stopped being true.
31
+ name: 'proof checking rejects an internally consistent document with a false computed value',
32
+ run: () => {
33
+ const source = [
34
+ 'birth(ada, 1815).',
35
+ 'age(P, A) :- birth(P, Y), A is 2026 - Y.',
36
+ '%% ?- age(ada, A).',
37
+ ].join('\n');
38
+ const program = Program.parseSources([{ text: source, filename: 'age-check.pl' }], { sourceMetadata: true });
39
+ const honest = [
40
+ 'age(ada, 211).',
41
+ 'step(age(ada, 211), rule(2), [\'P\' = ada, \'A\' = 211, \'Y\' = 1815], [birth(ada, 1815), 211 is 2026 - 1815]).',
42
+ 'step(birth(ada, 1815), fact(1), [], []).',
43
+ 'step(211 is 2026 - 1815, builtin, [], []).',
44
+ ].join('\n');
45
+ const honestReport = checkProofDocument(program, honest);
46
+ assertEqual(honestReport.valid, true, `honest proof checks: ${verdict(honestReport)}`);
47
+ assertEqual(honestReport.redecided, 1, 'the arithmetic step is recomputed, not trusted');
48
+
49
+ // The same document with 211 replaced by 999 everywhere. It stays
50
+ // internally consistent, so nothing but recomputation can catch it.
51
+ const lie = honest.replaceAll('211', '999');
52
+ const lieReport = checkProofDocument(program, lie);
53
+ assertEqual(lieReport.valid, false, 'a false computed value is rejected');
54
+ assertEqual(
55
+ [...new Set(lieReport.failures.map((failure) => failure.condition))].join(','),
56
+ 'C5',
57
+ 'only recomputation objects, so C1-C4 alone would have accepted it',
58
+ );
59
+ },
60
+ },
23
61
  {
24
62
  name: 'unification binds variables in Env',
25
63
  run: () => {
@@ -17,117 +17,13 @@ const examplesDir = path.join(packageRoot, 'examples');
17
17
  const expectedDir = path.join(examplesDir, 'output');
18
18
  const expectedProofDir = path.join(examplesDir, 'proof');
19
19
 
20
- export const proofExamples = [
21
- 'access-control-policy.pl',
22
- 'age.pl',
23
- 'aliases-and-namespaces.pl',
24
- 'ancestor.pl',
25
- 'animal.pl',
26
- 'annotation.pl',
27
- 'backward.pl',
28
- 'bayes-diagnosis.pl',
29
- 'cat-koko.pl',
30
- 'context-association.pl',
31
- 'data-negotiation.pl',
32
- 'deontic-logic.pl',
33
- 'derived-backward-rule.pl',
34
- 'derived-rule.pl',
35
- 'dog.pl',
36
- 'electrical-rc-filter.pl',
37
- 'existential-rule.pl',
38
- 'floating-point.pl',
39
- 'good-cobbler.pl',
40
- 'graph-reachability.pl',
41
- 'greatest-lower-bound-uniqueness.pl',
42
- 'group-inverse-uniqueness.pl',
43
- 'herbrand-semantics.pl',
44
- 'list-collection.pl',
45
- 'proof-contrapositive.pl',
46
- 'reusable-builtins.pl',
47
- 'snaf.pl',
48
- 'socrates.pl',
49
- 'spacecraft-battery-diagnosis.pl',
50
- 'term-tools.pl',
51
- 'witch.pl',
52
- 'beam-deflection.pl',
53
- 'cache-performance.pl',
54
- 'canary-release.pl',
55
- 'chart-parser.pl',
56
- 'clinical-trial-screening.pl',
57
- 'composition-of-injective-functions-is-injective.pl',
58
- 'diamond-property.pl',
59
- 'epidemic-policy.pl',
60
- 'equivalence-classes-overlap-implies-same-class.pl',
61
- 'expression-eval.pl',
62
- 'gdpr-compliance.pl',
63
- 'hanoi.pl',
64
- 'heat-loss.pl',
65
- 'herbrand-witnesses.pl',
66
- 'ideal-gas-law.pl',
67
- 'intuitionistic-logic-kripke.pl',
68
- 'linear-logic-resources.pl',
69
- 'modal-logic-kripke.pl',
70
- 'nixon-diamond.pl',
71
- 'security-incident-correlation.pl',
72
- 'symbolic-derivative.pl',
73
- 'trust-flow-provenance-threshold.pl',
74
- 'weighted-interval-scheduling.pl',
75
- 'integrity-check.pl',
76
- 'prime-range.pl',
77
- 'd3-group.pl',
78
- 'iso-operators.pl',
79
- 'web-names.pl',
80
- 'iso-dynamic-database.pl',
81
- 'partial-evaluator.pl',
82
- 'enigma1225.pl',
83
- 'rdf12-nested-triple-term.pl',
84
- 'rdf12-trig-triple-term.pl',
85
- 'rdf12-triple-term.pl',
86
- 'clpb-cardinality.pl',
87
- 'rdf12-trig-graph-join.pl',
88
- 'sieve.pl',
89
- 'vanilla-meta-interpreter.pl',
90
- 'reified-conditionals.pl',
91
- 'rdf12-directional-language.pl',
92
- 'json.pl',
93
- 'iso-extension-pipeline-audit.pl',
94
- 'universal-vs-existential-termination.pl',
95
- 'dif-constraints.pl',
96
- 'rdf12-trig-named-graph.pl',
97
- 'uuid.pl',
98
- 'combinatorics-findall-sort.pl',
99
- 'attributed-variables.pl',
100
- 'job-shop-scheduling.pl',
101
- 'dcg-command-parser.pl',
102
- 'knapsack-optimization.pl',
103
- 'knights-tour-warnsdorff.pl',
104
- 'declarative-fault-localization.pl',
105
- 'iso-integer-arithmetic.pl',
106
- 'workplace-compliance.pl',
107
- 'iso-extensions.pl',
108
- 'stable-marriage.pl',
109
- 'equality-saturation.pl',
110
- 'totient-summatory.pl',
111
- 'defeasible-reasoning.pl',
112
- 'clpb-boolean-circuit.pl',
113
- 'clpb-weighted-planning.pl',
114
- 'clpz-factorial.pl',
115
- 'clpz-global-constraints.pl',
116
- 'clpz-n-queens.pl',
117
- 'clpz-resource-allocation.pl',
118
- 'clpz-sudoku-9x9.pl',
119
- 'clpb-feature-model.pl',
120
- 'peasant.pl',
121
- 'iso-control-and-errors.pl',
122
- 'four-color-map.pl',
123
- 'catalan-convolution.pl',
124
- 'route-planning.pl',
125
- 'language.pl',
126
- 'rdf12-annotation.pl',
127
- 'iso-grouped-solutions.pl',
128
- 'cdcl-sat-solver.pl',
129
- 'propositional-dynamic-logic.pl',
130
- ];
20
+ // Every proof packaged under examples/proof/ is checked. This is read from
21
+ // the directory rather than listed here so that adding a proof cannot quietly
22
+ // add an unchecked one: a document nobody verifies is worse than no document,
23
+ // because it still looks like evidence.
24
+ export const proofExamples = fs.readdirSync(expectedProofDir)
25
+ .filter((name) => name.endsWith('.pl'))
26
+ .sort();
131
27
 
132
28
  export async function runExamples(reporter = new TestReporter()) {
133
29
  const files = fs.readdirSync(examplesDir)
@@ -34,23 +34,45 @@ const examplesDir = path.join(root, 'examples');
34
34
  const KNOWN_GAPS = new Map([
35
35
  ['clpb-weighted-planning.pl', 'an answer reached by optimising, which the replay would have to run again'],
36
36
  ['clpz-resource-allocation.pl', 'an answer reached by labeling, which the replay would have to run again'],
37
+ ['bulk-stream-write.pl', 'an answer produced by writing a stream, which the replay does not re-perform'],
38
+ ['pi.pl', 'an answer reached by numeric iteration the replay would have to run again'],
39
+ ['portable-library-overlap.pl', 'an answer about which library supplied a predicate, which the replay cannot restate'],
37
40
  ]);
38
41
 
39
- const totals = { steps: 0, verified: 0, trusted: 0 };
42
+ // Programs with no packaged proof, and why. Coverage is otherwise complete:
43
+ // every other example under examples/ has a checked explanation.
44
+ //
45
+ // iso-reflective-terms.pl copy_term/2 mints fresh variable names from a
46
+ // iso-term-io.pl counter that is global to the process, so two
47
+ // runs in one process already disagree. A golden
48
+ // that cannot be reproduced is not evidence.
49
+ // sbom-vulnerability-response.pl
50
+ // its proof records a directive as clause/3, and
51
+ // reading `(:- use_module(...))` back as an
52
+ // argument hits a parser limit: a parenthesised
53
+ // 1200-priority prefix term is rejected where
54
+ // SWI, Scryer and GNU Prolog accept it.
55
+ // takeuchi.pl proof generation does not finish in a minute.
56
+
57
+ const totals = { steps: 0, verified: 0, redecided: 0, trusted: 0 };
40
58
 
41
59
  export function runProofChecking(reporter = new TestReporter()) {
42
60
  reporter.section('Proof checking');
43
61
  totals.steps = 0;
44
62
  totals.verified = 0;
63
+ totals.redecided = 0;
45
64
  totals.trusted = 0;
46
65
  for (const name of [...proofExamples].sort()) {
47
66
  reporter.test(name, () => checkPackagedProof(name));
48
67
  }
49
68
  reporter.sectionTotal('proof checking');
50
- // What the corpus establishes, in the terms the specification uses: a
51
- // verified step was re-performed, a trusted one was recorded because
52
- // re-deciding it would mean running the program.
53
- reporter.section(`${totals.steps} steps, ${totals.verified} verified, ${totals.trusted} trusted`);
69
+ // What the corpus establishes, in the terms the conditions use: a verified
70
+ // step was re-performed against its source clause, a recomputed one was run
71
+ // again independently and agreed, and a trusted one is what the check still
72
+ // rests on rather than establishes.
73
+ reporter.section(
74
+ `${totals.steps} steps, ${totals.verified} verified, ${totals.redecided} recomputed, ${totals.trusted} trusted`,
75
+ );
54
76
  }
55
77
 
56
78
  function checkPackagedProof(name) {
@@ -60,6 +82,7 @@ function checkPackagedProof(name) {
60
82
  const report = checkProofDocument(program, proof);
61
83
  totals.steps += report.steps;
62
84
  totals.verified += report.verified;
85
+ totals.redecided += report.redecided;
63
86
  totals.trusted += report.trusted.length;
64
87
  const gap = KNOWN_GAPS.get(name);
65
88
  if (gap) {
@@ -9969,8 +9969,10 @@ Review questions:
9969
9969
  The [examples directory](https://github.com/eyereasoner/eyeprolog/tree/main/examples/) is the book's executable companion. The
9970
9970
  top-level directory contains **235 self-contained runnable programs**. Every
9971
9971
  source program has an exact answer file under
9972
- [examples/output](https://github.com/eyereasoner/eyeprolog/tree/main/examples/output/), and **109 selected programs** have a checked
9973
- explanation under [examples/proof](https://github.com/eyereasoner/eyeprolog/tree/main/examples/proof/). The thematic lists link every top-level program and open the program
9972
+ [examples/output](https://github.com/eyereasoner/eyeprolog/tree/main/examples/output/), and **231 selected programs** have a checked
9973
+ explanation under [examples/proof](https://github.com/eyereasoner/eyeprolog/tree/main/examples/proof/). Every one of those explanations is re-checked by `npm test`, which
9974
+ re-performs each recorded inference against its source clause and recomputes
9975
+ each primitive the document asserts. The thematic lists link every top-level program and open the program
9974
9976
  itself rather than merely naming it.
9975
9977
 
9976
9978
  [`examples/book/`](https://github.com/eyereasoner/eyeprolog/tree/main/examples/book/) mirrors the inline EyeProlog displays chapter by chapter. Those files are checked for syntax, and displays containing queries are executed, but some teaching fragments deliberately depend on neighboring facts or helpers. Use the top-level examples when you want a self-contained program with a golden answer; use `examples/book/` when you want the exact display being discussed on a page.