{
  "version": 1,
  "note": "Every case cites ISO or a SWI-Prolog transcript, or records a human adjudication. No case may cite a document belonging to either engine under test; scripts/comparison/corpus.py refuses that by name.",
  "cases": [
    {
      "id": "resolution.facts-and-rules-for-one-predicate",
      "family": "resolution-and-control",
      "termination": "complete",
      "clauses": [
        "p(a).",
        "q(b).",
        "p(X) :- q(X).",
        "r(X) :- p(X)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "r(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript",
        "note": "A fact and a rule for one predicate are tried in source order. The predecessor kept facts and rules in separate collections, so a rule-body call could drop every rule-derived solution."
      }
    },
    {
      "id": "resolution.conjunction-join",
      "family": "resolution-and-control",
      "termination": "complete",
      "clauses": [
        "p(a).",
        "p(b).",
        "q(b).",
        "q(c).",
        "both(X) :- p(X), q(X)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "both(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript"
      }
    },
    {
      "id": "resolution.cut-commits-to-the-clause",
      "family": "resolution-and-control",
      "termination": "complete",
      "clauses": [
        "a(1).",
        "a(2).",
        "b(2).",
        "p(X) :- a(X), !, b(X)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "p(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript",
        "note": "The cut commits to a(1); b(1) then fails, so the goal is false. ISO 13211-1 7.8.4."
      }
    },
    {
      "id": "resolution.negation-binds-nothing",
      "family": "resolution-and-control",
      "termination": "complete",
      "clauses": [
        "p(a).",
        "q(b).",
        "only(X) :- p(X), \\+ q(X)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "only(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript, ISO 13211-1 7.8.9"
      }
    },
    {
      "id": "builtins.arithmetic-integer-division-on-negatives",
      "family": "builtins",
      "termination": "complete",
      "clauses": [
        "t(A, B) :- A is -10 // 3, B is -10 div 3."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "t(A, B)",
          "variables": [
            "A",
            "B"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript, ISO 13211-1 9.1.3",
        "note": "// truncates toward zero, div floors. The predecessor used Python's floordiv for both."
      }
    },
    {
      "id": "builtins.sort-deduplicates-msort-does-not",
      "family": "builtins",
      "termination": "complete",
      "clauses": [
        "t(S, M) :- sort([b,a,b], S), msort([b,a,b], M)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "t(S, M)",
          "variables": [
            "S",
            "M"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript"
      }
    },
    {
      "id": "builtins.findall-keeps-multiplicity",
      "family": "builtins",
      "termination": "complete",
      "clauses": [
        "p(a).",
        "p(a).",
        "p(b).",
        "t(L) :- findall(X, p(X), L)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "t(L)",
          "variables": [
            "L"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript, ISO 13211-1 8.10.1"
      }
    },
    {
      "id": "builtins.univ-decomposes-a-compound",
      "family": "builtins",
      "termination": "complete",
      "clauses": [
        "t(L) :- f(a, b) =.. L."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "t(L)",
          "variables": [
            "L"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript, ISO 13211-1 8.5.3"
      }
    },
    {
      "id": "syntax.operator-precedence-in-a-comparison",
      "family": "syntax-and-terms",
      "termination": "complete",
      "clauses": [
        "t(X) :- X = 3, X > 1 + 1."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "t(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript, ISO 13211-1 operator table",
        "note": "X > 1 + 1 is >(X, +(1,1)). The predecessor's query parser read the right operand as a primary and lost the comparison entirely."
      }
    },
    {
      "id": "syntax.a-list-is-a-list-at-every-length",
      "family": "syntax-and-terms",
      "termination": "complete",
      "clauses": [
        "l([]).",
        "l([a]).",
        "l([a,b,c])."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "l(L)",
          "variables": [
            "L"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript"
      }
    },
    {
      "id": "value-fidelity.an-integer-a-float-and-an-atom-are-three-facts",
      "family": "value-fidelity",
      "termination": "complete",
      "topics": [
        "value-fidelity"
      ],
      "clauses": [
        "p(1).",
        "p('1').",
        "p(1.0)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "p(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript",
        "note": "Three distinct terms, so three facts and three answers. Measured: the predecessor collapses all three at storage and answers with one."
      }
    },
    {
      "id": "value-fidelity.a-compound-argument-survives-the-boundary",
      "family": "value-fidelity",
      "termination": "complete",
      "clauses": [
        "c(f(a, g(b), [1, 2.5]))."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "c(C)",
          "variables": [
            "C"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript",
        "note": "Nested compounds, a list and a float in one term: checks that the host boundary converts at every depth rather than only the top level."
      }
    },
    {
      "id": "recursion.transitive-closure-over-a-chain",
      "family": "recursion",
      "termination": "complete",
      "topics": [
        "auto-tabling"
      ],
      "clauses": [
        "edge(a, b).",
        "edge(b, c).",
        "edge(c, d).",
        "path(X, Y) :- edge(X, Y).",
        "path(X, Z) :- edge(X, Y), path(Y, Z)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "path(a, Y)",
          "variables": [
            "Y"
          ]
        }
      ],
      "oracle": {
        "kind": "adjudicated",
        "ref": "adjudication 2026-09-22: right-recursive closure over an acyclic chain",
        "note": "SWI is not used here because this kernel tables recursive predicates automatically and SWI does not, so discovery order may differ legitimately. The SET of answers is what is checked; order is multiset."
      },
      "expect": {
        "order": "multiset",
        "rows": [
          {
            "Y": {
              "t": "atom",
              "v": "b"
            }
          },
          {
            "Y": {
              "t": "atom",
              "v": "c"
            }
          },
          {
            "Y": {
              "t": "atom",
              "v": "d"
            }
          }
        ]
      }
    },
    {
      "id": "recursion.left-recursion-terminates-and-is-complete",
      "family": "recursion",
      "termination": "complete",
      "topics": [
        "auto-tabling"
      ],
      "clauses": [
        "edge(a, b).",
        "edge(b, c).",
        "path(X, Y) :- path(X, Z), edge(Z, Y).",
        "path(X, Y) :- edge(X, Y)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "path(a, Y)",
          "variables": [
            "Y"
          ]
        }
      ],
      "oracle": {
        "kind": "adjudicated",
        "ref": "adjudication 2026-09-22: the least model of a left-recursive closure",
        "note": "The least model is {b, c} whatever the evaluation strategy. SLD does not terminate on this program, which is why SWI is not the reference; an engine that loops or truncates is wrong about the model, not merely slower."
      },
      "expect": {
        "order": "multiset",
        "rows": [
          {
            "Y": {
              "t": "atom",
              "v": "b"
            }
          },
          {
            "Y": {
              "t": "atom",
              "v": "c"
            }
          }
        ]
      }
    },
    {
      "id": "recursion.an-infinite-answer-set-must-not-be-called-complete",
      "family": "errors-and-completion",
      "termination": "unbounded",
      "topics": [
        "status-channel"
      ],
      "clauses": [
        "nat(z).",
        "nat(s(X)) :- nat(X)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "nat(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "timeout": 25,
      "oracle": {
        "kind": "adjudicated",
        "ref": "adjudication 2026-09-22: soundness of a completeness claim",
        "note": "nat/1 has infinitely many solutions. Any finite answer set is therefore incomplete, and an engine that returns one while reporting completeness is unsound. Stopping -- a budget, an exhaustion status, a timeout -- is the correct behaviour and passes."
      }
    },
    {
      "id": "errors.an-open-generator-must-not-read-as-false",
      "family": "errors-and-completion",
      "termination": "unbounded",
      "topics": [
        "status-channel"
      ],
      "clauses": [
        "gen(L) :- member(_, L)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "gen(L)",
          "variables": [
            "L"
          ]
        }
      ],
      "timeout": 25,
      "oracle": {
        "kind": "adjudicated",
        "ref": "adjudication 2026-09-22: an empty answer set means false",
        "note": "member/2 on an unbound list generates indefinitely. Returning [] and calling it complete says the goal is FALSE, which it is not. Measured: the predecessor does exactly that, in 0.2 s."
      }
    },
    {
      "id": "mutation.a-returned-answer-list-is-the-callers-to-keep",
      "family": "mutation-and-isolation",
      "termination": "complete",
      "clauses": [
        "p(a).",
        "p(b)."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "p(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "swipl",
        "ref": "SWI-Prolog 10.0.2 transcript",
        "note": "This case exists to gate any future timing phase. Measured: the predecessor returns the SAME list object from a cached repeated query, so a caller appending to a result it was handed makes a later query answer with a fact the program does not entail. A warm repeated-query benchmark reports that cache as a 3,500x speedup."
      }
    },
    {
      "id": "errors.arithmetic-on-a-non-number-is-an-error",
      "family": "errors-and-completion",
      "termination": "complete",
      "clauses": [
        "t(X) :- X is a + 1."
      ],
      "operations": [
        {
          "id": "q1",
          "op": "query",
          "goal": "t(X)",
          "variables": [
            "X"
          ]
        }
      ],
      "oracle": {
        "kind": "adjudicated",
        "ref": "ISO 13211-1 9.1.7 type_error(evaluable, a/0); adjudicated 2026-09-22",
        "note": "An empty answer set would say the goal is false. It is not false: it is malformed. Both engines are judged on whether they raise rather than answer. SWI is not the reference because the two engines report errors through different channels (Prolog error terms vs typed Python exceptions), and the harness records 'raised' either way."
      },
      "expect": {
        "order": "sequence",
        "rows": [],
        "raises": true
      }
    },
    {
      "id": "capability.rule-level-explanation-of-a-derived-fact",
      "family": "mutation-and-isolation",
      "termination": "complete",
      "topics": [
        "provenance"
      ],
      "clauses": [
        "parent(tom, bob).",
        "parent(bob, ann).",
        "anc(X, Y) :- parent(X, Y)."
      ],
      "operations": [
        {
          "id": "probe",
          "op": "query",
          "goal": "derivation(anc(tom, bob))",
          "variables": []
        },
        {
          "id": "control",
          "op": "query",
          "goal": "derivation(anc(nobody, nobody))",
          "variables": []
        }
      ],
      "expect": {
        "nonempty": [
          "probe"
        ]
      },
      "oracle": {
        "kind": "adjudicated",
        "ref": "adjudication 2026-09-22: rule-level explanation is a capability, not a defect",
        "note": "The predecessor answers 'which rule fired, with which bindings' for ANY derived fact. Verified present: it returns rule_id, rule_text and bindings. The replacement answers only why a FLUENT holds at a time, so this capability was removed rather than relocated. This case exists so the corpus is not selected in the rewrite's favour. The negative control -- the same predicate on a fact that is not derivable -- stops a stub that answers everything from counting as support."
      }
    },
    {
      "id": "capability.why-a-fact-holds",
      "family": "mutation-and-isolation",
      "termination": "complete",
      "topics": [
        "provenance"
      ],
      "clauses": [
        "parent(tom, bob).",
        "parent(bob, ann).",
        "anc(X, Y) :- parent(X, Y)."
      ],
      "operations": [
        {
          "id": "probe",
          "op": "query",
          "goal": "why(parent(tom, bob))",
          "variables": []
        },
        {
          "id": "control",
          "op": "query",
          "goal": "why(parent(zz, zz))",
          "variables": []
        }
      ],
      "expect": {
        "nonempty": [
          "probe"
        ]
      },
      "oracle": {
        "kind": "adjudicated",
        "ref": "adjudication 2026-09-22: why/1 as a query predicate",
        "note": "Verified present in the predecessor. The replacement moved provenance onto the engine (kb.explain / kb.spans_for) and deliberately does not put it in the predicate namespace, so this is a declined capability rather than a missing one -- but it is still a capability the predecessor has and the replacement does not."
      }
    },
    {
      "id": "capability.evidence-chain-for-a-derived-fact",
      "family": "mutation-and-isolation",
      "termination": "complete",
      "topics": [
        "provenance"
      ],
      "clauses": [
        "parent(tom, bob).",
        "parent(bob, ann).",
        "anc(X, Y) :- parent(X, Y)."
      ],
      "operations": [
        {
          "id": "probe",
          "op": "query",
          "goal": "evidence_chain(anc(tom, bob))",
          "variables": []
        },
        {
          "id": "control",
          "op": "query",
          "goal": "evidence_chain(anc(zz, zz))",
          "variables": []
        }
      ],
      "expect": {
        "nonempty": [
          "probe"
        ]
      },
      "oracle": {
        "kind": "adjudicated",
        "ref": "adjudication 2026-09-22: nested evidence chain",
        "note": "Verified present in the predecessor, returning a nested rule_application tree."
      }
    }
  ]
}
