eyeprolog 1.6.13 → 1.6.15

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 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
@@ -1 +1 @@
1
- tak([34, 13, 8], 13).
1
+ tak([16, 11, 6], 11).
@@ -0,0 +1,80 @@
1
+ report(shape, shape(event, 2)).
2
+ report(payload, reading(temperature, 21)).
3
+ report(parts, [event, sensor_7, reading(temperature, 21)]).
4
+ report(rebuilt, alert(sensor_7, high)).
5
+ report(variable_count, 3).
6
+ report(copied_shape, same_but_fresh).
7
+ report(order, <).
8
+
9
+ clause(1, sample(event(sensor_7, reading(temperature, 21))), true).
10
+ clause(2,
11
+ report(shape, shape(var('Name'), var('Arity'))),
12
+ (sample(var('Term')), functor(var('Term'), var('Name'), var('Arity')))).
13
+ clause(3,
14
+ report(payload, var('Payload')),
15
+ (sample(var('Term')), arg(2, var('Term'), var('Payload')))).
16
+ clause(4, report(parts, var('Parts')), (sample(var('Term')), var('Term') =.. var('Parts'))).
17
+ clause(5, report(rebuilt, var('Term')), var('Term') =.. [alert, sensor_7, high]).
18
+ clause(6,
19
+ report(variable_count, var('Count')),
20
+ (term_variables(rule(var('X'), pair(var('X'), anonymous(1)), anonymous(2)), var('Variables')),
21
+ var('Variables') = [anonymous(3), anonymous(4), anonymous(5)],
22
+ var('Count') = 3)).
23
+ clause(7,
24
+ report(copied_shape, same_but_fresh),
25
+ (copy_term(pair(var('X'), var('X')), pair(var('A'), var('B'))),
26
+ var('A') == var('B'),
27
+ var('X') \== var('A'))).
28
+ clause(8, report(order, var('Order')), compare(var('Order'), alpha, beta)).
29
+
30
+ step(report(shape, shape(event, 2)),
31
+ rule(2),
32
+ ['Name' = event, 'Arity' = 2, 'Term' = event(sensor_7, reading(temperature, 21))],
33
+ [sample(event(sensor_7, reading(temperature, 21))),
34
+ functor(event(sensor_7, reading(temperature, 21)), event, 2)]).
35
+ step(sample(event(sensor_7, reading(temperature, 21))), fact(1), [], []).
36
+ step(functor(event(sensor_7, reading(temperature, 21)), event, 2), builtin, [], []).
37
+ step(report(payload, reading(temperature, 21)),
38
+ rule(3),
39
+ ['Payload' = reading(temperature, 21), 'Term' = event(sensor_7, reading(temperature, 21))],
40
+ [sample(event(sensor_7, reading(temperature, 21))),
41
+ arg(2, event(sensor_7, reading(temperature, 21)), reading(temperature, 21))]).
42
+ step(arg(2, event(sensor_7, reading(temperature, 21)), reading(temperature, 21)),
43
+ builtin,
44
+ [],
45
+ []).
46
+ step(report(parts, [event, sensor_7, reading(temperature, 21)]),
47
+ rule(4),
48
+ ['Parts' = [event, sensor_7, reading(temperature, 21)],
49
+ 'Term' = event(sensor_7, reading(temperature, 21))],
50
+ [sample(event(sensor_7, reading(temperature, 21))),
51
+ event(sensor_7, reading(temperature, 21)) =.. [event, sensor_7, reading(temperature, 21)]]).
52
+ step(event(sensor_7, reading(temperature, 21)) =.. [event, sensor_7, reading(temperature, 21)],
53
+ builtin,
54
+ [],
55
+ []).
56
+ step(report(rebuilt, alert(sensor_7, high)),
57
+ rule(5),
58
+ ['Term' = alert(sensor_7, high)],
59
+ [alert(sensor_7, high) =.. [alert, sensor_7, high]]).
60
+ step(alert(sensor_7, high) =.. [alert, sensor_7, high], builtin, [], []).
61
+ step(report(variable_count, 3),
62
+ rule(6),
63
+ ['Count' = 3, 'Variables' = [__anon0, __anon1, __anon2]],
64
+ [term_variables(rule(X, pair(X, _Y), _Z), [X, _Y, _Z]),
65
+ [__anon0, __anon1, __anon2] = [__anon0, __anon1, __anon2],
66
+ 3 = 3]).
67
+ step(term_variables(rule(X, pair(X, _Y), _Z), [X, _Y, _Z]), builtin, [], []).
68
+ step([__anon0, __anon1, __anon2] = [__anon0, __anon1, __anon2], builtin, [], []).
69
+ step(3 = 3, builtin, [], []).
70
+ step(report(copied_shape, same_but_fresh),
71
+ rule(7),
72
+ [],
73
+ [copy_term(pair(X, X), pair(__copy1_0, __copy1_0)),
74
+ __copy1_0 == __copy1_0,
75
+ X \== __copy1_0]).
76
+ step(copy_term(pair(X, X), pair(__copy1_0, __copy1_0)), builtin, [], []).
77
+ step(__copy1_0 == __copy1_0, builtin, [], []).
78
+ step(X \== __copy1_0, builtin, [], []).
79
+ step(report(order, <), rule(8), ['Order' = (<)], [compare(<, alpha, beta)]).
80
+ step(compare(<, alpha, beta), builtin, [], []).
@@ -0,0 +1,87 @@
1
+ report(first_term, event(sensor_7, online)).
2
+ report(variable_metadata, checked).
3
+ report(reached_end, yes).
4
+
5
+ clause(2, fixture_path('/tmp/eyeprolog-iso-term-io-example.pl'), true).
6
+ clause(4,
7
+ report(first_term, var('Term')),
8
+ (fixture_path(var('Path')),
9
+ open(var('Path'), read, var('Stream'), []),
10
+ read(var('Stream'), var('Term')),
11
+ close(var('Stream')))).
12
+ clause(5,
13
+ report(variable_metadata, checked),
14
+ (fixture_path(var('Path')),
15
+ open(var('Path'), read, var('Stream'), []),
16
+ read(var('Stream'), anonymous(1)),
17
+ read_term(var('Stream'), rule(var('A'), var('A'), var('B')), [variables([var('A'), var('B')]), variable_names([var('NameA') = var('A'), var('NameB') = var('B')]), singletons([var('NameB') = var('B')])]),
18
+ atom(var('NameA')),
19
+ atom(var('NameB')),
20
+ var('NameA') \== var('NameB'),
21
+ close(var('Stream')))).
22
+ clause(6,
23
+ report(reached_end, yes),
24
+ (fixture_path(var('Path')),
25
+ open(var('Path'), read, var('Stream'), [eof_action(eof_code)]),
26
+ read(var('Stream'), anonymous(1)),
27
+ read(var('Stream'), anonymous(2)),
28
+ read(var('Stream'), end_of_file),
29
+ at_end_of_stream(var('Stream')),
30
+ close(var('Stream')))).
31
+
32
+ step(report(first_term, event(sensor_7, online)),
33
+ rule(4),
34
+ ['Term' = event(sensor_7, online),
35
+ 'Path' = '/tmp/eyeprolog-iso-term-io-example.pl',
36
+ 'Stream' = '$stream'(1)],
37
+ [fixture_path('/tmp/eyeprolog-iso-term-io-example.pl'),
38
+ open('/tmp/eyeprolog-iso-term-io-example.pl', read, '$stream'(1), []),
39
+ read('$stream'(1), event(sensor_7, online)),
40
+ close('$stream'(1))]).
41
+ step(fixture_path('/tmp/eyeprolog-iso-term-io-example.pl'), fact(2), [], []).
42
+ step(open('/tmp/eyeprolog-iso-term-io-example.pl', read, '$stream'(1), []), builtin, [], []).
43
+ step(read('$stream'(1), event(sensor_7, online)), builtin, [], []).
44
+ step(close('$stream'(1)), builtin, [], []).
45
+ step(report(variable_metadata, checked),
46
+ rule(5),
47
+ ['Path' = '/tmp/eyeprolog-iso-term-io-example.pl',
48
+ 'Stream' = '$stream'(2),
49
+ 'NameA' = '_A',
50
+ 'NameB' = '_B'],
51
+ [fixture_path('/tmp/eyeprolog-iso-term-io-example.pl'),
52
+ open('/tmp/eyeprolog-iso-term-io-example.pl', read, '$stream'(2), []),
53
+ read('$stream'(2), event(sensor_7, online)),
54
+ read_term('$stream'(2), rule(_read_3_0, _read_3_0, _read_3_1), [variables([_read_3_0, _read_3_1]), variable_names(['_A' = _read_3_0, '_B' = _read_3_1]), singletons(['_B' = _read_3_1])]),
55
+ atom('_A'),
56
+ atom('_B'),
57
+ '_A' \== '_B',
58
+ close('$stream'(2))]).
59
+ step(open('/tmp/eyeprolog-iso-term-io-example.pl', read, '$stream'(2), []), builtin, [], []).
60
+ step(read('$stream'(2), event(sensor_7, online)), builtin, [], []).
61
+ step(read_term('$stream'(2), rule(_read_3_0, _read_3_0, _read_3_1), [variables([_read_3_0, _read_3_1]), variable_names(['_A' = _read_3_0, '_B' = _read_3_1]), singletons(['_B' = _read_3_1])]),
62
+ builtin,
63
+ [],
64
+ []).
65
+ step(atom('_A'), builtin, [], []).
66
+ step(atom('_B'), builtin, [], []).
67
+ step('_A' \== '_B', builtin, [], []).
68
+ step(close('$stream'(2)), builtin, [], []).
69
+ step(report(reached_end, yes),
70
+ rule(6),
71
+ ['Path' = '/tmp/eyeprolog-iso-term-io-example.pl', 'Stream' = '$stream'(4)],
72
+ [fixture_path('/tmp/eyeprolog-iso-term-io-example.pl'),
73
+ open('/tmp/eyeprolog-iso-term-io-example.pl', read, '$stream'(4), [eof_action(eof_code)]),
74
+ read('$stream'(4), event(sensor_7, online)),
75
+ read('$stream'(4), rule(_read_5_0, _read_5_0, _read_5_1)),
76
+ read('$stream'(4), end_of_file),
77
+ at_end_of_stream('$stream'(4)),
78
+ close('$stream'(4))]).
79
+ step(open('/tmp/eyeprolog-iso-term-io-example.pl', read, '$stream'(4), [eof_action(eof_code)]),
80
+ builtin,
81
+ [],
82
+ []).
83
+ step(read('$stream'(4), event(sensor_7, online)), builtin, [], []).
84
+ step(read('$stream'(4), rule(_read_5_0, _read_5_0, _read_5_1)), builtin, [], []).
85
+ step(read('$stream'(4), end_of_file), builtin, [], []).
86
+ step(at_end_of_stream('$stream'(4)), builtin, [], []).
87
+ step(close('$stream'(4)), builtin, [], []).