How Prolog corroborates the proof that Core Logic is not paraconsistent

Table of Contents

Dedicated to Jan Wielemaker, whose SWI-Prolog — and its WebAssembly incarnation — make the executable model of this proof something a reader can run in a browser, with nothing to install.

booting SWI-Prolog (WebAssembly)…

The proof that Core Logic is not paraconsistent is certified three times over — in Coq, in Lean 4 and in Athena — where a proof object is checked against a small trusted kernel. The accompanying Prolog file, core_logic_is_not_paraconsistent.pl, is a different kind of object: it does not certify, it corroborates. This note says, precisely, why the corroboration is worth having, by closing three doors (corresponding to section 1, 2 and 3) an objector might try to keep open. Door one: the five rules the Prolog engine runs are, line for line, the five constructors of the Coq Inductive — and those five are all of Table 1, the whole of the fragment \(\mathcal{F}\), with nothing smuggled in. Door two: those rules prove real theorems of minimal logic, including the very formula the anonymous correspondent put forward — corroborated by the Prolog search and, on the same base, kernel-certified by Coq with zero axioms — so the failure to derive the two Claims is a discriminating failure, not the impotence of a crippled encoding. Door three: the deductive step of the theorem is reconstructed, section by section, exactly as the command main. runs it — search, construction, and the two branches of the contradiction — and every proof tree shown below is the one the machine emits, not a hand-set copy.

Let me restate the epistemic frame in one paragraph, because the whole value of the exercise depends on not overclaiming. Prolog is not a theorem verifier. It is a logic-programming language with a fixed SLD-resolution strategy, here programmed to act as a proof checker over a bounded search. Its trust base is not a kernel but the entire hand-written program, plus our reading of what its execution means. Derivability is a bounded proof search (for(Th,0,6), deepening on \(L\to\)); the two antisequents rest on the failure of that search under the closed-world assumption. “No derivation of depth \(\le 6\) was found” becomes “no derivation exists” only by an argument made outside Prolog. That is corroboration in the strict sense: a claim that has survived a genuine attempt at refutation, not a claim checked against a kernel.

But corroboration of that kind still bites, for a reason the three assistants cannot match. Having no closed-world semantics, Coq, Lean and Athena cannot even attempt to refute the underivability of the Claims: they must posit each antisequent as a hypothesis — an axiom — and reason from it. The Prolog model does attempt the refutation, exhausts an iterative-deepening search over the finite fragment, and comes back empty-handed. Where the kernels assume the antisequent, the search tries and fails to break it. The two are complementary, not interchangeable. What follows shows the corroboration is not loose talk: each of its parts is a finite, exhibitable fact.

1. The running rules are exactly \(\mathcal{F}\)

The companion document Correspondence of the Fragment F rules already lines up Table 1 against Coq, Lean, Athena and Prolog, clause by clause. Here I only drive the nail: I take the Coq declaration and show, first, that the Prolog engine runs the same five rules, and second — the decisive point — that these five are all the rules there are.

The Coq derivability relation is one Inductive with exactly five constructors:

Inductive derivable :
  fragment_F -> set formula -> option formula -> Prop :=
| Ax          : forall f G A,     In A G -> derivable f G (Some A)
| L_neg       : forall f G A,     In (Neg A) G -> derivable f G (Some A) -> derivable f G None
| R_arrow     : forall f G A B,   derivable f (A :: G) (Some B) -> derivable f G (Some (Impl A B))
| L_arrow     : forall f G A B C, In (Impl A B) G -> derivable f G (Some A) -> derivable f (B :: G) C -> derivable f G C
| R_arrow_core: forall G A B,     derivable core_logic (A :: G) None -> derivable core_logic G (Some (Impl A B)).

The Prolog engine has one axiom clause and four rule clauses — one for each remaining constructor:

ax(Gamma:A) :- A \= (_=>_), A \= bot, iso_member(A, Gamma).       % Ax

rules(lneg,       Gamma:bot,   Rest:A)          :- iso_select(~A, Gamma, Rest).   % rules(rcond,      Gamma:A=>B,  [A|Gamma]:B)     :- B \= bot.                       % R→
rules(rcond_core, Gamma:A=>_B, [A|Gamma]:bot).                                     % R→C
rules(lcond,      Gamma:C,     conj(Rest:A, [B|Rest]:C)) :- iso_select(A=>B, Gamma, Rest). % L→

Read the two side by side and the map is total and injective both ways: axAx, lnegL_neg, rcondR_arrow, rcond_coreR_arrow_core, lcondL_arrow. The membership form (iso_member, iso_select) is Coq’s In A G to the letter; the empty succedent that Coq writes None is the object constant bot in succedent position; a conditional identity is derived, not initial, which is exactly why the axiom clause guards A \ (=>)=. These are encoding conventions, set out in the companion note, not extra rules.

The decisive move is the closure argument, and it belongs to the kind of object an Inductive is. An inductive definition is closed: its constructors are the only ways to build an inhabitant. So Coq’s five constructors are not “five rules among others we happen to use” — they are, provably, every way a sequent can be derived in \(\mathcal{F}\). Print Assumptions confirms the definition is closed under the global context. The Prolog side has, correspondingly, exactly five heads — ax/1 plus four rules/3 clauses — and no more. There is nowhere for a hidden rule to hide: an objector who suspects the underivability of the Claims is an artifact of some smuggled-in principle can enumerate the clauses and find that the list is closed, finite, and identical to Table 1. The first four rules are parametric in the fragment f (they hold under the minimal reading \(\mathcal{F}_{\mathbf M}\) and the Core reading \(\mathcal{F}_{\mathbb C}\) alike); only rcond_core is restricted to core_logic. That single Core-specific rule is the whole of the extra strength Core grants over the minimal fragment — and, as the theorem shows, the whole of what breaks the symmetry on the fatal sequent.

2. The rules prove theorems, the failure is discriminating

A skeptic’s natural worry: perhaps the engine fails on \(\lnot A, A \vdash B\) simply because it is too weak to prove anything interesting — in which case the failure would be worthless. This door closes with a single run. Take the formula the anonymous correspondent himself put forward, \[ (p \to (q \to r)) \to (q \to (p \to r)), \] the permutation-of-antecedents theorem — a genuine theorem of minimal (indeed of pure implicational) logic. Consult the file and click: the engine answers PROVABLE and draws its tree — built, and rendered, by the very clauses that compute it.

?- prove.

Two things about this derivation matter. First, it uses only ax, rcond and lcond — that is, only Ax, \(R\to\) and \(L\to\). No \(L\lnot\), and crucially no rcond_core: the theorem lives squarely in the minimal fragment \(\mathcal{F}_{\mathbf M}\), with no appeal to the one Core-specific rule and none whatever to ex falso. (One can check this mechanically: the tree carries no \(L\lnot\) and no \(R\to_{\mathbb C}\) step.) Second, note that the file’s fragment. command already verifies the sequent form \(A\to(B\to C) \vdash B \to (A \to C)\); the correspondent’s formula is just its fully-discharged, theorem-form cousin. His challenge formula was, in essence, already in the test suite.

So the engine is not impotent: it proves the theorems minimal logic ought to prove, by the rules of \(\mathcal{F}\), and it does so using only the sub-fragment that excludes ex falso. When that same engine then fails on \(\lnot A, A \vdash B\), the failure is discriminating. It is not “this system proves nothing”; it is “this system proves the genuine theorems and refuses precisely ex-falso-shaped sequents” — which is exactly the behaviour that makes \(\mathcal{F}\) a fragment of minimal, and not of intuitionistic, logic.

2.1. The same theorem, certified: the Coq twin of the Prolog tree

It is worth pausing here, because this is the very formula whose earlier mis-encoding prompted us to correct the certifications. So let us put the two objects side by side, on the same formula, and see that they agree.

On the same three rules — Ax, \(R\to\), \(L\to\) — Coq certifies what Prolog corroborated above. The calculus is not a second, look-alike system: the formula, fragment_F and derivable declarations used here are copied verbatim from Block A of the frozen Version-6 certification, and the code is byte-for-byte identical to it (same SHA256 on the declarations, comments aside). The theorem is proved for arbitrary formulae \(p, q, r\), by exactly the shared minimal rules — no L_neg, no R_arrow_core, no context_monotonicity:

Theorem exchange_weakening_regression :
  forall p q r : formula,
    derivable minimal_F []
      (Some (Impl (Impl p (Impl q r)) (Impl q (Impl p r)))).
Proof.
  intros p q r.
  apply R_arrow. apply R_arrow. apply R_arrow.
  apply L_arrow with (A := p) (B := Impl q r).
  - simpl. right. right. left. reflexivity.
  - apply Ax. simpl. left. reflexivity.
  - apply L_arrow with (A := q) (B := r).
    + simpl. left. reflexivity.
    + apply Ax. simpl. right. right. left. reflexivity.
    + apply Ax. simpl. left. reflexivity.
Qed.

Print Assumptions exchange_weakening_regression.
(* Closed under the global context *)

The last line is the certification proper: Print Assumptions returns Closed under the global context — the theorem rests on zero axioms. (Note: this regression theorem is not part of Version 6 itself — the frozen appendix does not need the objector’s formula; it is reproduced here, on Version 6’s own base, purely as a companion check. The three certifications are untouched.)

So the objector’s formula is settled twice over, in two proof languages that share nothing but the five rules of \(\mathcal{F}\):

  • Coq certifies it — a proof object checked against the kernel, closed under the global context, zero axioms;
  • Prolog corroborates it — a proof term found by search under the same three rules —

and the two derivations have the same shape, because it is the same derivation. The one asymmetry is a bonus on the Prolog side: it does not merely report success, it hands back the picture — the bussproofs proof tree shown above is emitted by render_bussproofs/1 from the very term it computed, ready to drop into the paper. Same theorem, same fragment, no ex falso on either side; Coq gives the kernel-checked certificate, Prolog gives the certificate’s portrait.

3. The theorem, section by section, as main. runs it

The command main. walks the article. I follow the same order, showing at each step the machine’s own output, so that a reader can run the file and read the paper in parallel.

3.1. Step 1 — \(\mathcal{F}\) proves the minimal theorems, and the two Claims fail

fragment. first confirms the standard minimal theorems (\(\vdash a \to a\); \(a\to b, a \vdash b\); transitivity of \(\to\); \(\vdash a \to (b \to a)\); permutation; \(\vdash \lnot a \to (a \to b)\)), then reports the two Claims underivable:

~a, a |- b   (Claim 1) : UNPROVABLE
~a, a |- ~b  (Claim 2) : UNPROVABLE

This is the search failing to refute the antisequents — the first half of the corroboration.

3.2. Step 2 — DNS.1 and DNS.2 are derived rules of \(\mathcal{F}\)

dns_rules. builds the two derivations of the paper’s Table 2: DNS.1, and DNS.2 — the right branch of the contradiction, and the one place the Core-specific rule enters. Click to see each, exactly as the engine emits it (\(\Delta = [\lnot a]\), \(A=a\), \(B=b\)):

?- dns1.    ?- dns2.

Note where the Core-specific rule enters: \(R\to_{\mathbb C}\) discharges \(A\) against the empty succedent obtained from \(\lnot A, A \vdash\). This is the single point at which \(\mathcal{F}_{\mathbb C}\) does what \(\mathcal{F}_{\mathbf M}\) cannot, and it is what makes the DNS.2 conclusion derivable in \(\mathbb C\).

3.3. Step 3 — DNS.1 is invertible: two independent corroborations of the induction

The invertibility of DNS.1 is the pivot of the proof, and it is established by structural induction on derivations — type-checked in both Coq and Lean. The Prolog file adds two independent confirmations, by routes those inductions do not take. First, dns1_sem_inv. verifies it semantically, by a Quine-style truth-table reduction over the three relevant valuations. Second, dns1_inv_cut. verifies it syntactically, by an admissible Cut: since the contexts at issue are consistent — Tennant’s own condition for Cut in \(\mathbb C\), and one that holds a fortiori in \(\mathbf M\) — the inversion goes through as a single Cut on the formula \((A\to B)\to B\), whose left premiss is a Slaney-style double-negation introduction. Click to see that Cut derivation:

?- dns1_inv_cut.

Three roads — induction, truth tables, Cut — reach the same summit: corroboration by convergence, the same result re-derived by means that share no machinery with the certified one.

3.4. Step 4 — from invertibility to \(\overline{DNS.1}\)

dns1_anti. reads the invertibility as an equivalence of premiss and conclusion, and takes its contrapositive: if the premiss is underivable, so is the conclusion. That is the anti-rule \(\overline{DNS.1}\), confirmed on concrete instances (e.g. \(a \nvdash b\) and \((a\to b)\to b \nvdash b\)).

3.5. Step 5 — the contradiction (theorem.)

Now the two branches meet. theorem. confirms Claim 1 underivable, confirms the DNS.2 conclusion derivable, and assembles the contradiction square. Click to assemble it:

?- theorem.

Read the clash on the two occurrences of \(\lnot A, (A\to B)\to B \; ? \; B\): the left branch, from Claim 1 through \(\overline{DNS.1}\), says the sequent is not derivable; the right branch, through DNS.2, exhibits a derivation of it. A sequent both underivable and derived: that is the contradiction. The final bar is deliberately unlabelled — it is a metatheoretic clash of a sequent with its own refutation, not a natural-deduction negation elimination.

3.6. Step 6 — the corollary

corollary. runs the same machine with \(\lnot B\) in place of \(B\): Claim 2 (\(\lnot A, A \vdash \lnot B\)) underivable, its DNS.2 conclusion \((A \to \lnot B)\to \lnot B,\ \lnot A \vdash \lnot B\) derivable, same clash, same \(\bot\).

?- corollary.

4. One further door: permissive and strict modes bracket the result

There is a last worry an objector could raise about the search itself: the default engine (main.) uses permissive axioms that admit weakening on the left at the leaves, for efficiency. Does that permissiveness taint the verdicts? No — for two reasons.

First, the weakening is less un-Core than it looks. Left-weakening — adjoining an unused hypothesis to the context — is not a primitive rule of \(\mathbb C\), but it is Core-admissible in exactly the regime these sequents inhabit: so long as the context is not explicitly inconsistent and no derivation of \(\lnot A, A \vdash \lnot B\) is in play. Its propositional witness is that \(A \vdash B \to A\) is Core-provable — Tennant’s vacuous discharge, the diamond on the discharge stroke, which the strict run derives explicitly as \(B \vdash A \to B\) [1, p. 35]. The one regime where left-weakening would be genuinely illicit in Core is the inconsistent context, where unrestricted weakening would simply be ex falso — and that is exactly the case Core is built to refuse, and exactly the case the weakening-free strict run re-checks below. The permissive axiom is thus a matter of efficiency, not a smuggled-in non-Core principle.

Second, the two modes bracket the result from opposite sides. The permissive engine proves a superset of what strict Core proves (weakening only adds derivations). Therefore, for the negative results, permissive failure is the stronger fact: if even the weakening-admitting engine cannot derive \(\lnot A, A \vdash B\), then strict Core — a fortiori — cannot. The over-approximation makes the antisequents more robust, not less. For the positive results, one wants the opposite guarantee, and core_strict. supplies it: with strictly singleton axioms \([C]:C\), explicit context-splitting on \(L\to\), and Tennant’s diamond on the discharge stroke (2017, p. 21), it re-derives the load-bearing sequents without any weakening at all:

A |- A                                   : DERIVABLE (strict Core OK)
|- A => A                                : DERIVABLE (strict Core OK)
A=>B, A |- B                             : DERIVABLE (strict Core OK)
B |- A=>B         (R-> vacuous)          : DERIVABLE (strict Core OK)
~A, A |-          (L~ on axiom)          : DERIVABLE (strict Core OK)
~A |- A=>B        (DNS.2 left premiss)   : DERIVABLE (strict Core OK)
(A=>B)=>B, ~A |- B      (DNS.2 concl.)   : DERIVABLE (strict Core OK)
(A=>~B)=>~B, ~A |- ~B   (Corollary)      : DERIVABLE (strict Core OK)

~A, A |- B    (Claim 1, must fail)       : NOT DERIVABLE (as expected)
~A, A |- ~B   (Claim 2, must fail)       : NOT DERIVABLE (as expected)

Permissive search = an upper bound on provability, under which the Claims still fail; strict run = a lower bound, under which the positive derivations still succeed. The result holds at both ends. (The one line worth writing out for the reader who wants it airtight: strict-provable \(\subseteq\) permissive-provable, since permissive weakening subsumes the vacuous-discharge case that strict adds; hence permissive failure transfers to strict.)

5. What stays outside Prolog

To close without overclaiming: the one inference the model cannot make on its own is the step from “no derivation of depth \(\le 6\) was found” to “no derivation exists”. That the bound is adequate for these shallow sequents is an argument about the search space, made outside Prolog — and it is exactly the inference the three kernels replace by taking the antisequent as an audited hypothesis. That is the seam between corroboration and certification, and it is the only one. Everything on the Prolog side of it — the rules are \(\mathcal{F}\) and all of \(\mathcal{F}\); the rules prove the real theorems; the two branches of the contradiction are searched for and constructed before one’s eyes — is a finite, runnable, exhibitable fact.

6. Run it

Nothing here needs to be taken on trust: consult core_logic_is_not_paraconsistent.pl at SWI-Tinker (in the browser, nothing to install) and type main., or any single command — fragment., dns_rules., dns1_sem_inv., dns1_inv_cut., dns1_anti., theorem., corollary., core_strict.. All sources — the three frozen certifications and this Prolog model — are in the GitHub repository; the certifications are untouched and their SHA256 hashes unchanged.

— for Jan Wielemaker, again: the model runs where it does because SWI-Prolog runs where it does.

7. References

1.
Tennant, N. Core Logic. Oxford University Press, 2017, 1st ed.

Published: 2026-07-21

Moderator: away from the internet from 22 to 26 July 2026; no comment can be published on this site during this period.