eyeprolog 1.6.14 → 1.6.16
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/README.md +1 -0
- package/arc-in-eyeprolog.md +226 -0
- package/package.json +2 -1
- package/src/check-proof.js +53 -1
- package/src/cli.js +9 -2
- package/test/regression/cases-regression.mjs +15 -2
- package/the-art-of-eyeprolog.md +8 -4
package/README.md
CHANGED
|
@@ -64,6 +64,7 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
|
|
|
64
64
|
|
|
65
65
|
- [The Art of EyeProlog](https://eyereasoner.github.io/eyeprolog/the-art-of-eyeprolog) — complete reference
|
|
66
66
|
- [Why EyeProlog?](https://eyereasoner.github.io/eyeprolog/why-eyeprolog) — project scope and design
|
|
67
|
+
- [ARC in EyeProlog](https://eyereasoner.github.io/eyeprolog/arc-in-eyeprolog) — answer, reason, check
|
|
67
68
|
- [Playground](https://eyereasoner.github.io/eyeprolog/playground) — run EyeProlog in a browser
|
|
68
69
|
- [Examples](examples) — runnable programs and checked output
|
|
69
70
|
- [Example decks](examples/deck/README.md) — explainable RDF/Prolog scenarios with reproducible roundtrips
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# ARC in EyeProlog — answer, reason, check
|
|
2
|
+
|
|
3
|
+
> Ask the right question. Get the answer. Understand the reason. Run the check.
|
|
4
|
+
|
|
5
|
+
[ARC](https://josd.github.io/arc/) turns a precise question into a portable,
|
|
6
|
+
executable artifact that answers it, explains the derivation, and checks the
|
|
7
|
+
result through a route capable of finding errors. This page is that pattern
|
|
8
|
+
written in EyeProlog's own terms, where the artifact is an ordinary Prolog
|
|
9
|
+
program, the reason is ordinary Prolog data, and the check is a command.
|
|
10
|
+
|
|
11
|
+
## 01 — Motivation: why a reasoner should check itself
|
|
12
|
+
|
|
13
|
+
Answers are cheap. Trustworthy answers are not.
|
|
14
|
+
|
|
15
|
+
A rule engine that returns `true` has told you that some search succeeded. It
|
|
16
|
+
has not told you which rules fired, which facts they rested on, or whether the
|
|
17
|
+
arithmetic along the way was right. In most workflows verification is a manual
|
|
18
|
+
afterthought: read the output, redo the calculation by hand, ask whoever wrote
|
|
19
|
+
the rules.
|
|
20
|
+
|
|
21
|
+
EyeProlog moves that work into the artifact:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
typical workflow program → answer → someone checks it by hand
|
|
25
|
+
EyeProlog program → answer + proof → checked mechanically
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This puts the valuable human work upstream — stating the question precisely,
|
|
29
|
+
choosing the facts, writing the rules honestly — and leaves the mechanical
|
|
30
|
+
work to something that can be run again tomorrow.
|
|
31
|
+
|
|
32
|
+
An answer does not become trustworthy by being emitted confidently. It becomes
|
|
33
|
+
trustworthy by being inspectable, repeatable, and able to be shown wrong.
|
|
34
|
+
|
|
35
|
+
## 02 — Pattern: how an EyeProlog arc works
|
|
36
|
+
|
|
37
|
+
Every arc starts from three explicit inputs, and in EyeProlog all three live in
|
|
38
|
+
one file:
|
|
39
|
+
|
|
40
|
+
- **Question** — what do we want to decide? Written as a goal: `%% ?- ageAbove(X0, X1).`
|
|
41
|
+
- **Data** — the facts: `birthDay(patH, '1944-08-21').`
|
|
42
|
+
- **Logic** — the rules: `ageAbove(S, A) :- birthDay(S, B), ..., F @> A.`
|
|
43
|
+
|
|
44
|
+
From those it produces three outputs:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
INPUT OUTPUT
|
|
48
|
+
Question ─┐ ┌─ Answer what follows
|
|
49
|
+
Data ─────┼─→ EyeProlog ───────┼─ Reason why it follows
|
|
50
|
+
Logic ────┘ └─ Check whether that why holds
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Run against [`examples/age.pl`](examples/age.pl), the three are three commands:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
eyeprolog examples/age.pl # Answer
|
|
57
|
+
eyeprolog --proof examples/age.pl # Reason
|
|
58
|
+
eyeprolog examples/age.pl --check-proof examples/proof/age.pl # Check
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
ageAbove(patH, 'P80Y').
|
|
63
|
+
checked: 7 steps, 2 recomputed.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The question is part of the specification, not a prompt. `%% ?- ageAbove(X0, X1).`
|
|
67
|
+
is precise enough to distinguish a right answer, a wrong answer, and a right
|
|
68
|
+
answer to a different question — and it stays in the file, so the artifact
|
|
69
|
+
carries what it was asked.
|
|
70
|
+
|
|
71
|
+
Nothing here is trapped in a session. The program is a text file, the proof is
|
|
72
|
+
a text file of ordinary Prolog facts, and the check is a process exit status.
|
|
73
|
+
|
|
74
|
+
## 03 — Trust: the check is the trust contract
|
|
75
|
+
|
|
76
|
+
An explanation is not verification. A convincing explanation can be produced
|
|
77
|
+
for a wrong result, so the check must be capable of **disagreeing** with the
|
|
78
|
+
answer.
|
|
79
|
+
|
|
80
|
+
A proof document is read as a claim, not believed. `--check-proof` tests five
|
|
81
|
+
conditions:
|
|
82
|
+
|
|
83
|
+
| | condition | what it establishes |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| **C1** | Resolution | every checked step really is an instance of the clause it cites — and the clause is taken from the program, not from the document, so a proof cannot be made valid by restating the rule it used |
|
|
86
|
+
| **C2** | Well-founded | following what a step used never leads back to it; a proof that rested on itself would prove anything |
|
|
87
|
+
| **C3** | Justification | every step carries exactly one known justification |
|
|
88
|
+
| **C4** | Coverage | every claim has a step, and every use resolves to a step or to a statement the program gives |
|
|
89
|
+
| **C5** | Re-decision | a step the document only *asserts* is computed again, independently, and must agree |
|
|
90
|
+
|
|
91
|
+
C5 is the trust contract. The largest class of steps in a typical proof is the
|
|
92
|
+
primitive — arithmetic, comparison, string and date operations that no clause
|
|
93
|
+
derives. Reading a document cannot tell whether those are true, so they are run
|
|
94
|
+
again against a program holding the bundled libraries and **nothing else**. No
|
|
95
|
+
clause of the theory under proof is present, which means the recomputation
|
|
96
|
+
cannot be talked into agreeing by the very rules it is auditing.
|
|
97
|
+
|
|
98
|
+
This is ARC's "genuinely different route", made concrete: not a second
|
|
99
|
+
implementation of the same reasoning, but the same primitive decided by a
|
|
100
|
+
process that has been denied the theory.
|
|
101
|
+
|
|
102
|
+
The difference is not theoretical. A document can pass C1 through C4 completely
|
|
103
|
+
— every step a proper instance, every use accounted for, no cycles — and still
|
|
104
|
+
record a computed value that is simply false, provided it tells the same lie
|
|
105
|
+
throughout. Only recomputation catches that one. There is a regression test
|
|
106
|
+
that constructs exactly such a document and requires the checker to reject it,
|
|
107
|
+
with C5 as the only objecting condition.
|
|
108
|
+
|
|
109
|
+
A condition that cannot fail is not much of a check.
|
|
110
|
+
|
|
111
|
+
## 04 — Composition: a checked answer is data
|
|
112
|
+
|
|
113
|
+
An arc need not stay isolated. Its output is the same kind of thing as its
|
|
114
|
+
input, which is what lets arcs compose.
|
|
115
|
+
|
|
116
|
+
A proof document is not a log; it is a set of ground Prolog facts — `step/4`,
|
|
117
|
+
`clause/3`, and the claims themselves. That means the checked answer of one
|
|
118
|
+
program can be loaded as the data of the next, and a program can reason *about*
|
|
119
|
+
a proof as readily as it reasons about anything else.
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
program A ──→ answer + proof ──┐
|
|
123
|
+
program B ──→ answer + proof ──┼──→ program C ──→ answer + proof + check
|
|
124
|
+
facts ────────────────────-┘
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The same property connects EyeProlog to the wider data world. RDF quads convert
|
|
128
|
+
to ordinary `rdf/4` facts and back, so an arc can take RDF in, reason in
|
|
129
|
+
Prolog, and emit RDF out, with the proof of the middle step available for
|
|
130
|
+
inspection. The contract at each boundary is explicit, so a larger arc can
|
|
131
|
+
check not only its own result but whether the pieces compose into an answer to
|
|
132
|
+
the larger question.
|
|
133
|
+
|
|
134
|
+
## 05 — Scope: what this can and cannot guarantee
|
|
135
|
+
|
|
136
|
+
Checking makes trust testable. It does not make computation infallible, and
|
|
137
|
+
the checker says which is which rather than folding everything into an
|
|
138
|
+
undifferentiated success.
|
|
139
|
+
|
|
140
|
+
- A check is only as strong as its independence. C5's independence comes from
|
|
141
|
+
excluding the theory under proof; that same exclusion is why some steps
|
|
142
|
+
cannot be recomputed at all.
|
|
143
|
+
- **Reflective** goals — those reading the program's own database or operator
|
|
144
|
+
table — are outside C5 by construction, since that is exactly what it
|
|
145
|
+
excludes.
|
|
146
|
+
- **Stateful** goals — an attributed variable, a constraint store, an open
|
|
147
|
+
stream — depend on state the original run accumulated. Re-running stream
|
|
148
|
+
operations would also perform I/O, and a checker must not have side effects.
|
|
149
|
+
- These remain **obligations**: named individually in the checker, counted
|
|
150
|
+
separately in the result, and reported as what the check rests on rather
|
|
151
|
+
than what it establishes.
|
|
152
|
+
- A proof does not authenticate its source data. Correct reasoning over wrong
|
|
153
|
+
facts gives a correctly derived wrong answer.
|
|
154
|
+
- Negation as failure means a goal did not succeed. It does not establish that
|
|
155
|
+
the negated statement is false.
|
|
156
|
+
- Explicit rules can still encode the wrong policy. Being able to read the
|
|
157
|
+
derivation is what makes that reviewable.
|
|
158
|
+
|
|
159
|
+
Human judgment stays essential, particularly in choosing the question and
|
|
160
|
+
deciding what evidence is enough.
|
|
161
|
+
|
|
162
|
+
## 06 — Applications: where this fits
|
|
163
|
+
|
|
164
|
+
Rule-driven work with explicit structure and testable correctness conditions:
|
|
165
|
+
|
|
166
|
+
- tracing a policy decision back to the facts and rules that produced it;
|
|
167
|
+
- recomputing an engineering or numeric result by an independent route;
|
|
168
|
+
- checking a derivation against invariants, bounds, or known identities;
|
|
169
|
+
- auditing a decision months later, from the proof alone, without rerunning
|
|
170
|
+
the search that found it;
|
|
171
|
+
- carrying a checked result across a system boundary, where the recipient
|
|
172
|
+
trusts neither the sender nor the sender's engine.
|
|
173
|
+
|
|
174
|
+
That last one is the case a proof is really for. The recipient does not have to
|
|
175
|
+
trust the engine that produced the answer — they can check the document against
|
|
176
|
+
the program themselves, with a different copy of the checker if they like.
|
|
177
|
+
|
|
178
|
+
## 07 — Practice: design principles
|
|
179
|
+
|
|
180
|
+
- **Question first** — state the goal in the file, not in a shell history.
|
|
181
|
+
- **Answer directly** — the answer is a term, not prose about a term.
|
|
182
|
+
- **Explain the derivation** — record the clause, the bindings, and what each
|
|
183
|
+
step rested on.
|
|
184
|
+
- **Check independently** — recompute what the document merely asserts, from a
|
|
185
|
+
position that cannot reuse the theory.
|
|
186
|
+
- **Fail visibly** — a proof that cannot be explained records the answer as
|
|
187
|
+
`unproven` rather than omitting it.
|
|
188
|
+
- **Name what is trusted** — an obligation counted and labelled is honest; an
|
|
189
|
+
obligation folded into a success is not.
|
|
190
|
+
- **Prefer executable verification** — checking runs in the test suite, not in
|
|
191
|
+
someone's afternoon.
|
|
192
|
+
- **Keep artifacts self-contained** — program, answer, and proof are three
|
|
193
|
+
files that can be read and rerun without the machine that made them.
|
|
194
|
+
|
|
195
|
+
## 08 — Catalogue: the corpus
|
|
196
|
+
|
|
197
|
+
The pattern is not aspirational here. Every one of the **235 examples** ships
|
|
198
|
+
with its answer and a checked proof, and `npm test` re-checks all of them on
|
|
199
|
+
every run:
|
|
200
|
+
|
|
201
|
+
| | |
|
|
202
|
+
| --- | ---: |
|
|
203
|
+
| examples | 235 |
|
|
204
|
+
| packaged proofs | 235 |
|
|
205
|
+
| recorded steps | 36423 |
|
|
206
|
+
| verified against a source clause | 20746 |
|
|
207
|
+
| recomputed independently | 14720 |
|
|
208
|
+
| remaining obligations | 952 (2.6%) |
|
|
209
|
+
|
|
210
|
+
The proof directory is read from disk rather than from a list, so a document
|
|
211
|
+
cannot be added without being checked. An unverified proof is worse than none,
|
|
212
|
+
because it still looks like evidence.
|
|
213
|
+
|
|
214
|
+
Worth reading in order: [`examples/age.pl`](examples/age.pl) for the smallest
|
|
215
|
+
complete arc, [`examples/deontic-logic.pl`](examples/deontic-logic.pl) for a
|
|
216
|
+
policy decision traced to its rules, and
|
|
217
|
+
[`examples/clpz-n-queens.pl`](examples/clpz-n-queens.pl) for a proof of a
|
|
218
|
+
constrained search.
|
|
219
|
+
|
|
220
|
+
## References
|
|
221
|
+
|
|
222
|
+
- [ARC — answer, reason, check](https://josd.github.io/arc/), the pattern this
|
|
223
|
+
page follows
|
|
224
|
+
- [Why EyeProlog?](why-eyeprolog.md), the same argument in the setting of an
|
|
225
|
+
ISO Prolog implementation
|
|
226
|
+
- [The Art of EyeProlog](the-art-of-eyeprolog.md), the implementation reference
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.6.
|
|
6
|
+
"version": "1.6.16",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"LICENSE.md",
|
|
30
30
|
"README.md",
|
|
31
|
+
"arc-in-eyeprolog.md",
|
|
31
32
|
"index.js",
|
|
32
33
|
"index.d.ts",
|
|
33
34
|
"playground.html",
|
package/src/check-proof.js
CHANGED
|
@@ -351,8 +351,10 @@ export function checkProofDocument(program, text) {
|
|
|
351
351
|
failures.push({ condition: 'C4', conclusion: key(claim), detail: 'claimed, but no step concludes it' });
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
|
+
let uses = 0;
|
|
354
355
|
for (const step of steps) {
|
|
355
356
|
for (const use of step.uses) {
|
|
357
|
+
uses++;
|
|
356
358
|
if (byConclusion.has(key(use)) || given.has(key(use))) continue;
|
|
357
359
|
failures.push({ condition: 'C4', conclusion: key(step.conclusion), detail: `uses ${key(use)}, which nothing concludes and the program does not give` });
|
|
358
360
|
}
|
|
@@ -360,7 +362,57 @@ export function checkProofDocument(program, text) {
|
|
|
360
362
|
|
|
361
363
|
checkWellFounded(byConclusion, failures);
|
|
362
364
|
|
|
363
|
-
|
|
365
|
+
const failed = (condition) => failures.filter((failure) => failure.condition === condition).length;
|
|
366
|
+
// What each condition covered, so a reader can see the shape of the check
|
|
367
|
+
// rather than only its verdict. A condition that examined nothing says so:
|
|
368
|
+
// "0 steps" is information, not a pass.
|
|
369
|
+
const conditions = [
|
|
370
|
+
{ id: 'C1', name: 'Resolution', covered: verified, failed: failed('C1'),
|
|
371
|
+
summary: `${verified} step(s) re-performed against the source clause they cite` },
|
|
372
|
+
{ id: 'C2', name: 'Well-founded', covered: steps.length, failed: failed('C2'),
|
|
373
|
+
summary: `${steps.length} step(s) checked for a conclusion resting on its own derivation` },
|
|
374
|
+
{ id: 'C3', name: 'Justification', covered: steps.length, failed: failed('C3'),
|
|
375
|
+
summary: `${steps.length} step(s) carrying exactly one known justification` },
|
|
376
|
+
{ id: 'C4', name: 'Coverage', covered: claims.length + uses, failed: failed('C4'),
|
|
377
|
+
summary: `${claims.length} claim(s) and ${uses} use(s) resolved to a step or to a statement the program gives` },
|
|
378
|
+
{ id: 'C5', name: 'Re-decision', covered: redecided, failed: failed('C5'),
|
|
379
|
+
summary: `${redecided} step(s) recomputed against a program holding no clause of the theory` },
|
|
380
|
+
];
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
valid: failures.length === 0,
|
|
384
|
+
steps: steps.length,
|
|
385
|
+
verified,
|
|
386
|
+
redecided,
|
|
387
|
+
uses,
|
|
388
|
+
trusted,
|
|
389
|
+
failures,
|
|
390
|
+
claims: claims.length,
|
|
391
|
+
conditions,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// The condition-by-condition account, as lines. The obligations are listed
|
|
396
|
+
// under C5 because that is the condition they escaped: a step recorded as
|
|
397
|
+
// `builtin` that recomputation could not decide, or one whose justification
|
|
398
|
+
// puts it outside recomputation altogether.
|
|
399
|
+
export function conditionReport(report) {
|
|
400
|
+
const lines = [];
|
|
401
|
+
for (const condition of report.conditions ?? []) {
|
|
402
|
+
const mark = condition.failed > 0 ? `${condition.failed} failure(s)` : 'ok';
|
|
403
|
+
lines.push(` ${condition.id} ${condition.name.padEnd(13)} ${mark.padEnd(14)} ${condition.summary}`);
|
|
404
|
+
}
|
|
405
|
+
const obligations = report.trusted ?? [];
|
|
406
|
+
if (obligations.length > 0) {
|
|
407
|
+
const byReason = new Map();
|
|
408
|
+
for (const item of obligations) {
|
|
409
|
+
const reason = item.reason ?? item.kind;
|
|
410
|
+
byReason.set(reason, (byReason.get(reason) ?? 0) + 1);
|
|
411
|
+
}
|
|
412
|
+
const parts = [...byReason].map(([reason, count]) => `${count} ${reason}`).join(', ');
|
|
413
|
+
lines.push(` -- ${obligations.length} obligation(s) the check rests on rather than establishes: ${parts}`);
|
|
414
|
+
}
|
|
415
|
+
return lines;
|
|
364
416
|
}
|
|
365
417
|
|
|
366
418
|
// The program's clauses, numbered from 1 in load order -- the numbering
|
package/src/cli.js
CHANGED
|
@@ -252,16 +252,20 @@ export async function main(argv) {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
if (options.checkProof != null) {
|
|
255
|
-
const { checkProofDocument, verdict } = await import('./check-proof.js');
|
|
255
|
+
const { checkProofDocument, conditionReport, verdict } = await import('./check-proof.js');
|
|
256
256
|
const proofText = await fs.readFile(options.checkProof, 'utf8');
|
|
257
257
|
const report = checkProofDocument(program, proofText);
|
|
258
258
|
if (report.steps === 0) throw new Error(`no step/4 proof step found in ${options.checkProof}`);
|
|
259
259
|
if (!report.valid) {
|
|
260
|
+
// The conditions go to stderr alongside the failures, so a failing run
|
|
261
|
+
// still says which of the five objected and which held.
|
|
262
|
+
for (const line of conditionReport(report)) process.stderr.write(`${line}\n`);
|
|
260
263
|
for (const failure of report.failures.slice(0, 5)) {
|
|
261
264
|
process.stderr.write(` [${failure.condition}] ${failure.conclusion} -- ${failure.detail}\n`);
|
|
262
265
|
}
|
|
263
266
|
throw new Error(`${options.checkProof} is not a valid proof for this program: ${report.failures.length} failure(s)`);
|
|
264
267
|
}
|
|
268
|
+
if (!options.quiet) for (const line of conditionReport(report)) process.stdout.write(`${line}\n`);
|
|
265
269
|
process.stdout.write(`${verdict(report)}.\n`);
|
|
266
270
|
return;
|
|
267
271
|
}
|
|
@@ -396,7 +400,10 @@ Input:
|
|
|
396
400
|
Options:
|
|
397
401
|
-h, --help Show this help text and exit.
|
|
398
402
|
-p, --proof Enable proof explanations.
|
|
399
|
-
--proof-detail mode
|
|
403
|
+
--proof-detail mode abstract stops at a bundled library predicate and records
|
|
404
|
+
it as one builtin step; expanded explains through it,
|
|
405
|
+
as ordinary source steps. Only differs for a program
|
|
406
|
+
that calls a library. (implies --proof)
|
|
400
407
|
--check-proof file Check a saved proof document against the input program.
|
|
401
408
|
-q, --quads Run embedded quad tests and fail if any do not hold.
|
|
402
409
|
Note: -q is quads, not quiet; --quiet has no short form.
|
|
@@ -4348,15 +4348,28 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
4348
4348
|
fs.writeFileSync(proofFile, generated.stdout);
|
|
4349
4349
|
const verified = runCli(['--check-proof', proofFile, programFile]);
|
|
4350
4350
|
assertEqual(verified.status, 0, 'verification status');
|
|
4351
|
-
|
|
4351
|
+
// The run accounts for all five conditions and then gives its verdict,
|
|
4352
|
+
// so a reader sees which conditions the result rests on rather than
|
|
4353
|
+
// only that it passed.
|
|
4354
|
+
for (const condition of ['C1 Resolution', 'C2 Well-founded', 'C3 Justification', 'C4 Coverage', 'C5 Re-decision']) {
|
|
4355
|
+
assertIncludes(verified.stdout, condition, `verification reports ${condition}`);
|
|
4356
|
+
}
|
|
4357
|
+
assertEqual(verified.stdout.trimEnd().split('\n').pop(), 'checked: 2 steps.', 'verification verdict');
|
|
4358
|
+
assertEqual(verified.stdout.includes('obligation'), false, 'nothing was left trusted');
|
|
4359
|
+
const quiet = runCli(['--quiet', '--check-proof', proofFile, programFile]);
|
|
4360
|
+
assertEqual(quiet.stdout, 'checked: 2 steps.\n', 'quiet verification stdout is the verdict alone');
|
|
4352
4361
|
const strictVerified = runCli(['--iso-strict', '--check-proof', proofFile, programFile]);
|
|
4353
4362
|
assertEqual(strictVerified.status, 0, 'strict verification status');
|
|
4354
|
-
assertEqual(strictVerified.stdout, 'checked: 2 steps
|
|
4363
|
+
assertEqual(strictVerified.stdout.trimEnd().split('\n').pop(), 'checked: 2 steps.', 'strict verification verdict');
|
|
4355
4364
|
const tamperedFile = path.join(temp.dir, `proof-certificate-bad-${++temp.counter}.pl`);
|
|
4356
4365
|
fs.writeFileSync(tamperedFile, generated.stdout.replace('step(p(a),', 'step(p(b),'));
|
|
4357
4366
|
const rejected = runCli(['--check-proof', tamperedFile, programFile]);
|
|
4358
4367
|
assertEqual(rejected.status, 1, 'tampered verification status');
|
|
4359
4368
|
assertIncludes(rejected.stderr, 'is not a valid proof for this program', 'tampered verification stderr');
|
|
4369
|
+
// A failing run still accounts for every condition, so the report says
|
|
4370
|
+
// which one objected rather than only that something did.
|
|
4371
|
+
assertIncludes(rejected.stderr, 'C1 Resolution', 'tampered run still reports the conditions');
|
|
4372
|
+
assertIncludes(rejected.stderr, 'failure(s)', 'tampered run marks the objecting condition');
|
|
4360
4373
|
},
|
|
4361
4374
|
},
|
|
4362
4375
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1538,10 +1538,14 @@ Checking re-performs each step against the clause it cites: the clause must
|
|
|
1538
1538
|
exist, its variables bound as the step recorded them must yield exactly this
|
|
1539
1539
|
conclusion from exactly these uses, every claim must have a step, every use
|
|
1540
1540
|
must resolve to a step or to a statement the program gives, and no conclusion
|
|
1541
|
-
may rest on itself. `builtin
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1541
|
+
may rest on itself. A `builtin` step derives from no clause, so reading the
|
|
1542
|
+
document cannot decide it; it is computed again instead, against a program
|
|
1543
|
+
holding the bundled libraries and no clause of the theory under proof, and
|
|
1544
|
+
must agree. `absent`, `collected` and `asserted` steps stay trusted: the first
|
|
1545
|
+
two range over the theory that recomputation deliberately excludes, and a
|
|
1546
|
+
clause asserted at run time is in no source file to check against. The report
|
|
1547
|
+
counts what was verified, what was recomputed, and what remains an obligation
|
|
1548
|
+
rather than folding them into an undifferentiated success. An
|
|
1545
1549
|
answer the solver found but the explanation cannot reproduce is recorded as
|
|
1546
1550
|
`unproven`, which makes the document fail its check, because that is the
|
|
1547
1551
|
truth about it.
|