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.
- package/examples/proof/abstract-interpretation.pl +146 -0
- package/examples/proof/ackermann.pl +296 -0
- package/examples/proof/alignment-demo.pl +196 -0
- package/examples/proof/allen-interval-calculus.pl +1271 -0
- package/examples/proof/auroracare.pl +954 -0
- package/examples/proof/basic-monadic.pl +37176 -0
- package/examples/proof/bayes-therapy.pl +1349 -0
- package/examples/proof/bench.pl +2802 -0
- package/examples/proof/binomial-vandermonde.pl +983 -0
- package/examples/proof/blocks-world-planning.pl +471 -0
- package/examples/proof/bmi.pl +386 -0
- package/examples/proof/braking-safety-worlds.pl +508 -0
- package/examples/proof/buck-converter-design.pl +179 -0
- package/examples/proof/bulk-stream-write.pl +3 -0
- package/examples/proof/collatz-1000.pl +43784 -0
- package/examples/proof/competitive-enzyme-kinetics.pl +167 -0
- package/examples/proof/complex.pl +514 -0
- package/examples/proof/context-schema-audit.pl +374 -0
- package/examples/proof/continued-fraction-sqrt2.pl +476 -0
- package/examples/proof/control-system.pl +137 -0
- package/examples/proof/critical-path-schedule.pl +491 -0
- package/examples/proof/cross-organization-data-sharing.pl +732 -0
- package/examples/proof/cyclic-path.pl +44 -0
- package/examples/proof/dairy-energy-balance.pl +290 -0
- package/examples/proof/dcg-expression-language.pl +157 -0
- package/examples/proof/deep-taxonomy-10.pl +185 -0
- package/examples/proof/deep-taxonomy-100.pl +365 -0
- package/examples/proof/deep-taxonomy-1000.pl +163 -0
- package/examples/proof/deep-taxonomy-10000.pl +163 -0
- package/examples/proof/deep-taxonomy-100000.pl +163 -0
- package/examples/proof/delfour.pl +1615 -0
- package/examples/proof/dijkstra-findall-sort.pl +354 -0
- package/examples/proof/dijkstra-risk-path.pl +820 -0
- package/examples/proof/dijkstra.pl +753 -0
- package/examples/proof/dining-philosophers.pl +2663 -0
- package/examples/proof/donald-gerald-robert.pl +311 -0
- package/examples/proof/dpv-odrl-purpose-mapping.pl +1189 -0
- package/examples/proof/drone-corridor-planner.pl +2365 -0
- package/examples/proof/easter-computus.pl +1406 -0
- package/examples/proof/eulerian-path.pl +457 -0
- package/examples/proof/ev-range-worlds.pl +508 -0
- package/examples/proof/exoplanet-validation-worlds.pl +397 -0
- package/examples/proof/explainable-ev-depot-configuration.pl +313 -0
- package/examples/proof/family-cousins.pl +920 -0
- package/examples/proof/fast-fourier-transform.pl +5045 -0
- package/examples/proof/fastpow.pl +769 -0
- package/examples/proof/fft8-numeric.pl +685 -0
- package/examples/proof/fibonacci.pl +1425 -0
- package/examples/proof/field-nitrogen-balance.pl +320 -0
- package/examples/proof/flandor.pl +533 -0
- package/examples/proof/fundamental-theorem-arithmetic.pl +245 -0
- package/examples/proof/gd-step-certified.pl +1717 -0
- package/examples/proof/goldbach.pl +1135 -0
- package/examples/proof/gps.pl +627 -0
- package/examples/proof/graph.pl +105 -0
- package/examples/proof/gray-code-counter.pl +355 -0
- package/examples/proof/hamiltonian-path.pl +3373 -0
- package/examples/proof/hamming-code.pl +232 -0
- package/examples/proof/heron-theorem.pl +79 -0
- package/examples/proof/http-client.pl +0 -0
- package/examples/proof/illegitimate-reasoning.pl +220 -0
- package/examples/proof/integer-partitions.pl +1642 -0
- package/examples/proof/iso-atomic-conversion.pl +73 -0
- package/examples/proof/knowledge-engineering-alignment-flow.pl +200 -0
- package/examples/proof/knuth-bendix-completion.pl +290 -0
- package/examples/proof/law-of-cosines.pl +71 -0
- package/examples/proof/least-squares-regression.pl +342 -0
- package/examples/proof/lee.pl +637 -0
- package/examples/proof/lldm.pl +581 -0
- package/examples/proof/manufacturing-quality-control.pl +181 -0
- package/examples/proof/map-four-color-search.pl +796 -0
- package/examples/proof/markov-logic-network.pl +405 -0
- package/examples/proof/matrix-chain-order.pl +164 -0
- package/examples/proof/matrix-noncommutativity.pl +183 -0
- package/examples/proof/microgrid-dispatch.pl +164 -0
- package/examples/proof/missionaries-cannibals.pl +428 -0
- package/examples/proof/modular-exponentiation.pl +838 -0
- package/examples/proof/monkey-bananas.pl +278 -0
- package/examples/proof/network-sla.pl +100 -0
- package/examples/proof/newton-raphson.pl +520 -0
- package/examples/proof/observability-log-correlation.pl +1061 -0
- package/examples/proof/odrl-dpv-fpv-trust-flow.pl +1366 -0
- package/examples/proof/odrl-dpv-healthcare-risk-ranked.pl +488 -0
- package/examples/proof/odrl-dpv-risk-ranked.pl +637 -0
- package/examples/proof/odrl-policy-advanced.pl +705 -0
- package/examples/proof/odrl-policy-reasoning.pl +1423 -0
- package/examples/proof/odrl-policy.pl +103 -0
- package/examples/proof/operational-incident-response.pl +435 -0
- package/examples/proof/orbital-transfer-design.pl +288 -0
- package/examples/proof/path-discovery.pl +502 -0
- package/examples/proof/peano-arithmetic.pl +910 -0
- package/examples/proof/peano-calculus.pl +294 -0
- package/examples/proof/pell-equation.pl +295 -0
- package/examples/proof/pendulum-period.pl +97 -0
- package/examples/proof/pi.pl +3 -0
- package/examples/proof/pointer-analysis.pl +94 -0
- package/examples/proof/polynomial.pl +2487 -0
- package/examples/proof/portable-library-overlap.pl +6 -0
- package/examples/proof/quadratic-formula.pl +159 -0
- package/examples/proof/quine-mccluskey.pl +914 -0
- package/examples/proof/radioactive-decay.pl +104 -0
- package/examples/proof/rdf12-annotated-claims.pl +263 -0
- package/examples/proof/register-allocation.pl +120 -0
- package/examples/proof/relational-cube-lookup.pl +15867 -0
- package/examples/proof/riemann-hypothesis.pl +148 -0
- package/examples/proof/sat-solver-dpll.pl +226 -0
- package/examples/proof/scientific-evidence-graph.pl +681 -0
- package/examples/proof/send-more-money.pl +252 -0
- package/examples/proof/service-impact.pl +151 -0
- package/examples/proof/shoelace-polygon-area.pl +161 -0
- package/examples/proof/skolem-functions.pl +128 -0
- package/examples/proof/statistics-summary.pl +250 -0
- package/examples/proof/stirling-bell-numbers.pl +125 -0
- package/examples/proof/superdense-coding.pl +118 -0
- package/examples/proof/symbiotic-knowledge-graph.pl +1569 -0
- package/examples/proof/truth-maintenance-system.pl +466 -0
- package/examples/proof/turing.pl +1183 -0
- package/examples/proof/type-inference.pl +267 -0
- package/examples/proof/vector-similarity.pl +163 -0
- package/examples/proof/vulnerability-impact.pl +180 -0
- package/examples/proof/wolf-goat-cabbage.pl +339 -0
- package/examples/proof/zebra.pl +176 -0
- package/package.json +1 -1
- package/src/check-proof.js +159 -11
- package/test/conformance/NEUMERKEL-LATEST.md +3 -3
- package/test/regression/cases-white-box.mjs +38 -0
- package/test/run-examples.mjs +7 -111
- package/test/run-proof-checking.mjs +28 -5
- package/the-art-of-eyeprolog.md +4 -2
package/src/check-proof.js
CHANGED
|
@@ -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
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
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
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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** — **
|
|
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 |
|
|
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** | **
|
|
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: () => {
|
package/test/run-examples.mjs
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
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) {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -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 **
|
|
9973
|
-
explanation under [examples/proof](https://github.com/eyereasoner/eyeprolog/tree/main/examples/proof/).
|
|
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.
|