Entailment
purrdf-entail (re-exported as
purrdf::entail) is native, wasm32-clean entailment for the PurRDF
RdfDataset IR. A family of engines sits behind one facade, each the right
tool for its SPARQL entailment regime — closing a dataset to its inferred
fixpoint entirely in interned TermId space, with no external reasoner,
no async runtime, and no string round-trip.
Surface map
| Entry point | Regime(s) | Engine |
|---|---|---|
materialize(ds, regime) | Simple, RDF, RDFS, OWL-RL, D | Forward materialization (“chase”) of the regime’s declared clause program via a native semi-naive fixpoint. Returns (closure, ReasoningReport); the report is not optional. |
materialize_dl_reported(...), or materialize(ds, Materialization::OwlDirect(bgp)) | OWL-Direct | Open-world OWL DL over a SHOIQ(D) tableau, directed by the query’s basic graph pattern bgp; materialize delegates to it for this regime rather than restating it. |
materialize_rif(...) | RIF | RIF-Core rule entailment over a parsed RuleSet. |
parse_rif_xml(...) / resolve_rif_imports(...) | RIF | RIF-XML parsing with caller-owned, I/O-free import resolution. |
rules(regime) / implemented(regime) | — | The rule table a regime is defined by, and the subset this workspace fires. Their difference is the measurable gap. |
calculus_program(regime) | — | The regime’s calculus as DL-clause data — the very program materialize evaluates, so a consumer can recompute its contract hash. |
Regime::from_iri(iri) | — | Parse a sparql:entailmentRegime IRI to its enum. |
#![allow(unused)]
fn main() {
use purrdf::entail::{materialize, Completeness, Materialization};
let ds = purrdf::parse_dataset(
b"<https://example.org/alice> <https://example.org/name> \"Alice\" .",
"application/n-triples",
None,
)?;
// Close a frozen dataset to its RDFS fixpoint; the result is a new dataset
// AND a report of what the run did.
let (closed, report) = materialize(&ds, Materialization::Rdfs).expect("materializes");
assert_eq!(report.completeness(), Completeness::ExactWithinBoundaries);
assert!(closed.quad_count() > ds.quad_count());
Ok::<(), Box<dyn std::error::Error>>(())
}
The same engine in five hosts
Entailment is not re-implemented per host. The purrdf command line, Python,
WebAssembly, and the C ABI all route through one shared string boundary
(purrdf_validate::regime) that wraps the Rust engine, and the surfaces are
checked against a single committed golden-vector artifact — so a divergence shows
up as one vector failing rather than as several surfaces that quietly stopped
agreeing. The regime spellings
(simple, rdf, rdfs, owl-rl, owl-direct, rif, d) are the same
everywhere.
| Host | Materialize | Defined rule table | Implemented rules |
|---|---|---|---|
| Rust | materialize(&ds, Materialization::Rdfs) | rules(Regime::Rdfs) | implemented(Regime::Rdfs) |
| CLI | purrdf reason --regime rdfs, purrdf convert --entailment rdfs, purrdf query --entailment rdfs (and purrdf entails asks the conclusion-directed question) | — | — |
| Python | purrdf.entail.materialize(dataset, "rdfs", "", imports, premise_iris), purrdf.entail.materialize_nt(text, "rdfs", "", imports, premise_iris) | purrdf.entail.rules("rdfs") | purrdf.entail.implemented_rules("rdfs") |
| JavaScript / WebAssembly | entailMaterialize(doc, "rdfs", "", iris, docs, premiseIris) | entailRules("rdfs") | entailImplementedRules("rdfs") |
| C | purrdf_entail_materialize_to_nquads(...) | purrdf_entail_rules(...) | purrdf_entail_implemented_rules(...) |
Every host materializes every regime; none refuses one. What two regimes need is an
INPUT, and each host has a parameter for it: --rules <FILE> on the CLI, a
program string on the Python, WebAssembly and C surfaces, and the
Materialization value itself in Rust. rif takes a normative RIF-in-XML rule
document there; every other regime takes none, and supplying one is an error rather
than a discarded argument. owl-direct’s extra input is a query’s class
expressions, so a document-in/document-out call runs the query-independent
augmentation and a query surface (purrdf::query_with_entailment,
purrdf query --entailment owl-direct) is where the query-directed lane lives.
One host-specific note:
- The WebAssembly module also exports
entailCheckGoldenVectors(), which replays the committed tri-host vector artifact inside the module a consumer actually loaded — so agreement with the reference implementation can be checked without trusting this repository’s CI.
Asking a question instead: the conclusion-directed services
Materializing answers “what does this premise entail?”. The three services below answer “does this premise entail that?”, which is a different question and not the membership test in a closure it looks like: a conclusion’s blank nodes are existentials that have to be mapped, an inconsistent premise entails everything, a failure to find a mapping means nothing unless the rule set is complete for the premise it ran on — and a conclusion can be entailed while appearing nowhere in the closure at all, which is what the five mechanisms beyond the rule table exist for (see Conformance).
They run over the same shared boundary, on all five host shapes, and the
purrdf command line is one of them. scripts/check-entailment-surface.py is the
gate: it derives the service set from purrdf-entail’s own public entry points and
fails until every one of them is reachable from every host with the boundary’s
whole parameter list, so a service or a parameter cannot land on four hosts and go
dark on the fifth. It also mutation-tests itself on every run — one edit per check,
applied in memory over the committed tree, each of which must make the gate fail —
because a check that cannot withhold a green light is not a check.
| Host | Does P entail C? | Re-decide the warrant | Certain answers of a pattern |
|---|---|---|---|
| Rust | entails(&p, &c, Regime::OwlRl, &imports) | verify(warrant, &p, &c) | certain_answers(&p, &bgp, Regime::OwlRl, &imports) |
| CLI | purrdf entails --regime owl-rl --premise P --conclusion C | … --conclusion C --verify | … --pattern BGP |
| Python | purrdf.entail.graph_entails("owl-rl", p, c, imports, premise_iris) | purrdf.entail.verify_entailment(...) | purrdf.entail.certain_answers("owl-rl", p, bgp, imports, premise_iris) |
| JavaScript / WebAssembly | entailGraphEntails("owl-rl", p, c, iris, docs, premiseIris) | entailVerifyEntailment(...) | entailCertainAnswers(...) |
| C | purrdf_entail_graph_entails(...) | purrdf_entail_verify_entailment(...) | purrdf_entail_certain_answers(...) |
Two things differ from the materializing table above, and both are consequences of the question rather than of any host:
- Five regimes, not seven.
owl-directis directed by a query’s class expressions andrifentails under the caller’s rule document, and “premise, conclusion, regime” carries neither. Both are refused by name on every host — never answered under a weaker regime and labelled with the one that was asked for — and both still materialize. - The import table is a parameter, on every host. An ontology’s imports closure
is the ontology, so a premise carrying an
owl:importsthe call was not handed is a different premise. PurRDF fetches nothing, so the closure arrives as caller-supplied configuration: an ordered list of(ontology IRI, document)pairs, spelled--import IRI=FILEon the command line. An import needs no pair when it names the premise document itself — the CLI’sfile://retrieval IRI or--base, and on the Python, WebAssembly and C hosts thepremise_irisargument (premiseIrisin JavaScript), the IRIs the caller read the premise from,[]for bare text — or a graph the premise already holds (<X> a owl:Ontology,<X> a sh:ShapesGraph, or anowl:versionIRInaming it). Any other unresolved import is a refusal naming the document, never a silently truncated premise, and a table entry the premise’s closure never reaches is refused too — it would be read and never used — on every host, as SHACL validation refuses an unused shapes-graph entry. Every other entailment service takes the same table and applies the same rule: materialization (CLIreason,convert --entailment; Pythonmaterialize/materialize_nt; WebAssemblyentailMaterialize; Cpurrdf_entail_materialize_to_nquads; Rustmaterialize_with_imports), the entailment-regime query (CLIquery --entailment … --import IRI=FILE; PythonStore.query_entailment_governed(..., imports=…, premise_iris=…); WebAssemblyqueryEntailmentGoverned’simportIris/importDocuments/premiseIrisoptions; Cpurrdf_query_entailment_governed’simport_iris/import_documents/import_count; Rustquery_with_entailment_closure_governed), and the OWL-Direct consistency question (CLIconsistency --import IRI=FILE; Pythonconsistency(data, imports, premise_iris); WebAssemblyentailConsistency(doc, iris, docs, premiseIris, …); Cpurrdf_entail_consistency). Each closes or decides the merged premise, statesontology-import-resolvedin its report or certificate, and refuses an unresolved import or an unreached entry exactly asreasondoes. The Rust entry points that take no table (query_with_entailment,query_with_entailment_governed) resolve against the empty one, so a premise that imports a document it does not hold is refused there too, never closed without it. Theowl:importsread are the same ones SHACL validation reads: on the premise’s own IRI, on anowl:Ontologyheader, on ash:ShapesGraph(sh:RulesGraphand subclasses included), or on a node naming one of those as itsowl:versionIRI. Anowl:importson any other node, a node that is only ash:DataGraphincluded, is an ordinary premise triple.
A pattern is N-Triples with ?name (or $name) in any position, the predicate
included. RDF reserves that position for an IRI, so the boundary reaches it by
rewriting each variable to a term drawn from a namespace it has swept out of the
caller’s own text and mapping every occurrence back afterwards; nothing of that
namespace reaches a row, a binding or a report. The one slot that admits no variable
is a literal’s datatype: "5"^^?d asks for a binding in a position that holds an
IRI rather than a term, and it is refused by name — the stand-in must never be left
sitting there, matching the boundary’s own namespace instead of the caller’s data.
A predicate variable is projected
like any other, and under owl-rl it also renders a limit: it ranges over the
whole predicate vocabulary, so it ranges over the schema predicates, which the
theorem behind the rule table’s completeness does not cover. That theorem is
Theorem PR1 of
OWL 2 Profiles §4.3,
and its hypothesis has two halves: the premise lies inside the OWL 2 RL syntax, and
the conclusion is assertional — class assertions over class names, property
assertions, owl:sameAs and owl:differentFrom over named individuals. Only when
both hold is the rule table complete, so a schema conclusion is one the table claims
no completeness for, whether or not scm-* derives it. The predicate variable also
ranges over the constructs the mechanisms beyond the table decide, for which the
closure the rows are drawn from holds nothing.
Every answer arrives with the certificate of the run underneath it — the same
purrdf-reasoning-report block a materialization renders, plus a mechanism line
naming which of the six reached the verdict.
Rule coverage
rules(regime) is the rule table the specification defines the regime by;
implemented(regime) is the subset the evaluator fires. Both are &'static
slices in specification table order, so the gap is an executable artifact instead
of a sentence:
| Regime | Rule table | Defined | Implemented |
|---|---|---|---|
Simple | — (identity closure) | 0 | 0 |
RDF | RDF 1.2 Semantics §8.1.1 | 3 | 3 |
RDFS | RDF 1.2 Semantics §8.1.1 + §9.2.1 | 18 | 18 |
OWL-RL | OWL 2 Profiles §4.3 Tables 4–9 | 78 | 78 |
D | OWL 2 Profiles §4.3 Table 8 | 5 | 5 |
OWL-Direct | — (SHOIQ(D) tableau, not a fixed table) | 0 | 0 |
RIF | — (caller-supplied rule set) | 0 | 0 |
The per-rule breakdown — every rule id, its specification citation, and whether it is fired — is generated from that API and drift-guarded, so it cannot fall behind the code.
Where the numbers stop:
- The four existential rules fire, but their conclusions are withheld.
rdfD1,rdfD1a,rdfs14andrdfs14aeach conclude about a fresh blank node. The restricted chase mints each one as a frontier-addressed Skolem witness and closes under it, so the rules genuinely fire — but every conclusion mentioning a surrogate is dropped when the closure is materialized back, because a SPARQL entailment regime draws its answers from the scoping graph and a surrogate is not in it. The withholding is reported asConstruct::Surrogate. Nothing surrogate-free is lost: replacing a term with a fresh blank node only weakens a triple. - A complete rule table is not a complete closure.
OWL-RLfires all 78 rules, and a run that met a boundary still reportsCompleteness::ExactWithinBoundariesrather thanExact. The two claims are reported separately on purpose. Nor is a complete rule table entailment conformance: on this vendored W3C corpus of OWL 2 RL entailment testsentails()reaches 27 of 27 published positive entailments, and agrees with W3C on 23 of 23 negative ones — 3 of those 23 refuted, a decided non-entailment, and 20 admitted, the closure computed and observed not to contain the non-conclusion. Read23 of 23as “no unsoundness found”, never as “23 non-entailments proved” (see Conformance below). 78 / 78 says every rule of Tables 4–9 is implemented — and the one W3C-published entailment that is reachable only by a sound rule outside those tables is reached by an extension,ext-eq-diff-sym, whichextensions(Regime::OwlRl)names, neitherrules()norimplemented()names, and every report renders on its ownextensionline. Eight more are reached by refutation rather than by matching, and those add no rule at all: see the conformance section below. - Seventeen OWL 2 RL rules conclude
false. “Implemented” for those means decided: a body match becomesEntailError::Inconsistentcarrying a witness that names the rule and the asserted triples that satisfied it. That is the only thing a rule with no conclusion can do.
The chase (Simple / RDF / RDFS / OWL-RL / D)
materialize runs a forward-materialization chase: a fixed rule set for the
selected regime, applied by a semi-naive fixpoint until no new quads appear.
Because it runs over the frozen IR, it is deterministic — a given input and
regime always yields the same closure — and because it works in TermId
space, no term is ever re-parsed or re-serialized along the way.
Typical use: materialize first, then query with the plain SPARQL engine or validate the closure with SHACL (the SHACL validator itself performs no inference).
The rule set is not written twice. calculus_program(regime) renders it as
DL clauses and materialize evaluates exactly those clauses through
purrdf-datalog’s semi-naive evaluator, so the contract hash a
report carries identifies the clauses that actually ran.
The RDF, RDFS, OWL-RL and D lanes run under purrdf-datalog’s
evaluation limits: the facts
each graph’s store may hold and the candidate solutions its rules may
enumerate. The defaults are 4,194,304 facts and 1,048,576 join steps
natively, and 131,072 and 1,048,576 in WebAssembly. A caller that needs other
limits states them — materialize_with(ds, plan, &options, stop) in Rust,
--max-stored-facts and --max-join-steps on purrdf reason and purrdf convert --entailment, max_stored_facts= and max_join_steps= on Python’s
materialize and materialize_nt, maxStoredFacts and maxJoinSteps on
entailMaterialize, and max_stored_facts and max_join_steps on
purrdf_entail_materialize_to_nquads. A run past a limit is refused, naming the
limit, the numbers and that host’s knob; a run inside the limits returns the
same closure any larger limits would, and its report’s contract hash names the
calculus under the limits in force.
Every other service that evaluates a regime’s rule table takes the same two
limits, and names the knob of the entry point the caller called — never a Rust
type to a caller who cannot reach one. The conclusion-directed services hold the
premise’s closure and every re-chase of the refutation and freeze mechanisms to
them: entails_with and certain_answers_with in Rust (a MaterializeLimits on
the string boundary), --max-stored-facts and --max-join-steps on purrdf entails, keyword-only max_stored_facts= and max_join_steps= on Python’s
certain_answers, graph_entails and verify_entailment, trailing
maxStoredFacts and maxJoinSteps on entailCertainAnswers,
entailGraphEntails and entailVerifyEntailment, and max_stored_facts and
max_join_steps after premise_iri_count on the three C entry points. The
entailment-regime query holds its closure to them: EntailmentClosure::with_limits
in Rust, --max-stored-facts and --max-join-steps beside purrdf query --entailment, max_stored_facts= and max_join_steps= on
Store.query_entailment_governed, the maxStoredFacts and maxJoinSteps options
of queryEntailmentGoverned, and the two parameters of
purrdf_query_entailment_governed; the query governors still price only the
evaluation over the closure. The OWL-Direct consistency question runs no rule
table: its evaluator is the hypertableau, bounded by step_cap and work_cap on
every host, so it takes no stored-fact or join-step knob that would govern
nothing.
Every run says what it did
materialize returns (closure, ReasoningReport). There is deliberately no
report-free variant, because the alternative — two entry points, one of which
discards the evidence — is how a partial rule set comes to be described as a
complete one. The report carries:
Completeness— derived fromrules(regime)minusimplemented(regime), so it improves by itself as rules are added, and it names themissingrules rather than merely counting them;- per-rule firing counts — which rules fired and how many conclusions each contributed;
Boundarys — the constructs the run met and could not close over, each with its reason;- the evaluation budget — what the run consumed against the evaluation limits it ran under;
- a contract hash —
purrdf-datalog’s digest of the clause program and the evaluation limits in force, so a cached closure minted under a different calculus, or under different limits, can be refused rather than trusted; - an inconsistency witness, when a rule that concludes
falsematched: the rule id, the asserted triples that satisfied its premises in premise order, and the graph they were read from.
A report cannot claim Exact while naming a boundary. ReasoningReport stores
no completeness field at all: completeness() derives the value from the
boundary list itself, so the contradictory state is unrepresentable rather than
merely checked. That is deliberate — an earlier design stored the field and
compared it against a derivation of the same inputs, which is vacuous by
construction and could never fail.
The rendering is byte-stable, so the Python, WebAssembly, and C hosts hand back the same report text as Rust for the same input.
OWL-Direct: the tableau
OWL-Direct semantics is open-world Description Logic, which a forward chase
cannot answer. materialize_dl_reported runs an SHOIQ(D) tableau instead —
answering instance and subsumption queries via classification, realization,
and query-directed materialization. Because it needs the query’s class
expressions, it takes them as its own query_bgp parameter; materialize
reaches the same tableau by delegating to it for Materialization::OwlDirect
rather than restating it.
RIF
materialize_rif evaluates RIF-Core rules over a parsed RuleSet,
covering the SPARQL RIF entailment regime.
D (datatype) entailment
D is materialized, not refused. PurRDF realizes it as Simple entailment plus
the five dt-* rules of OWL 2 Profiles §4.3 Table 8 — the fixed rule table a
forward chase can enumerate for it — decided over the XSD value space by
purrdf-xsd rather than by comparing lexical forms.
What Table 8 does not cover is the infinite value spaces themselves, and that is
reported as a Construct::DatatypeValueSpace boundary on the run rather than
claimed. So a D closure is complete within its stated boundary, and the report
is where the boundary is stated.
Every host materializes d, the command-line tool included.
There is no unsupported-regime error. materialize takes a Materialization,
which carries each regime’s own input — a basic graph pattern for OWL-Direct, a
RuleSet for RIF — so each of the seven alternatives dispatches to its engine.
Invalid input, inconsistency and exhausted resource limits return typed errors.
Regime stays as the reporting and identity type that ReasoningReport::regime(),
rules(), implemented() and Regime::from_iri speak in.
Invariants
- No minted vocabulary. Every constant in the crate’s
vocabmodule is a standardrdf:/rdfs:/owl:IRI drawn from the entailment specs themselves — the crate fabricates none, per the toolkit-not-ontology rule. - Dependency-lean and wasm-clean. The dependencies are
purrdf-core,purrdf-datalog,purrdf-xsd,purrdf-lex,purrdf-iri,purrdf-hash, andhashbrown’s raw hash table, keyed bypurrdf-core’s fixed-key hasher — every one of themwasm32-unknown-unknown-clean, so the engines carry into Rust, Python, WebAssembly, and C unchanged, with no threads, filesystem, or RNG dependency. - Deterministic. Same input + regime → same closure, always — and the same report, byte for byte.
Conformance
Two corpora measure two different things, and the distinction matters:
-
W3C SPARQL 1.1 entailment-regime group — 70 of 70 cases pass, with zero ledgered residuals: the RDF/RDFS/OWL-RL chase, the OWL-Direct (DL) tableau, the RIF-Core rule engine, and RDF-axiomatic predicate typing, all run through the SPARQL conformance harness.
-
W3C OWL 2 test suite — 262 of 262 cases agree, 0 ledgered, zero unledgered. This corpus is consistency-shaped: all 262 vendored cases are
otest:ConsistencyTest(226) orotest:InconsistencyTest(36). It therefore grades the DL/tableau lane’s satisfiability verdicts and says nothing about the OWL 2 RL rule table. The typed ledger contains 0 divergences. Every disagreement and every stale ledger entry remains a hard failure.Two things this row does not say. First, the upstream material is not free of entailment tests — the W3C manifest holds 206 positive and 23 negative entailment tests; this corpus lacks them because the flattening it was taken from extracted the premise literal and discarded the conclusion literal, which is exactly the half an entailment grade needs. They are vendored and graded by the next bullet. Second, the corpus is a subset: 262 of the 482 consistency-shaped cases upstream. Of the 220 it leaves out, 172 the tableau decided when the exclusion was measured (108 consistent, 64 inconsistent), 0 did not terminate under a 40 s ceiling, 25 were withheld (20 reasoner, 5 parse), and 23 carry no RDF/XML premise — so the exclusion was payload triage, not a capability limit, and “262 of 262” is a number over a corpus rather than over what W3C published.
Those five figures are a dated measurement, recorded in
census.tsv’sdl_probecolumn and described in that suite’sPROVENANCE.md. The harness reads the column and prints it on every run; it does NOT re-run the reasoner over the 220 excluded cases, so this row cannot detect a regression among them. Re-deriving them means re-running the probe, which is a deliberate act rather than part of the gate. -
W3C OWL 2 RL entailment tests — 50 of 50 cases agree, 0 ledgered, zero unledgered. This is the independent oracle for the rule table: W3C’s own entailment tests, answered by one call to
purrdf_entail::entails()per case underRegime::OwlRl. The two lanes prove different things and are reported separately.The negative lane is 23 of 23: no unsoundness. The chase never derived a triple W3C publishes as not entailed. That is the safety result, and it holds over all 23 negative cases — soundness is owed on every case, so none were filtered by profile.
Those 23 agreements are two different results, and the harness prints the split on a scoreboard line of its own:
OWL2-RL-NEGATIVE: total 23 = refuted 3 + admitted 20 (premise-outside-rl 5, conclusion-outside-rl 10, construct-not-read 5, refutation-budget 0, freeze-budget 0, data-range-containment 0) + unsound 0 + withheld 0Three are decided non-entailments — both halves of Theorem PR1’s hypothesis hold, so the closure’s failure to contain the non-conclusion is a proof. The other 20 admit: the closure was computed and does not contain the non-conclusion, which is the whole of the soundness observation, and nothing beyond it is claimed. Both agree, and correctly so; what they differ in is discriminating power, since a reasoner that derived nothing at all would score
negative 23 of 23withrefuted 0. Read23 of 23as “no unsoundness found”, never as “23 non-entailments proved”.The positive lane is 27 of 27 — the 27 positive entailments W3C itself places inside the RL profile under RDF-Based semantics — and the typed divergence ledger
purrdf_sparql_conformance::owl2_rl::LEDGERis EMPTY — 0schema-conclusion, 0negative-conclusion, 0construct-outside-rl, 0imports-unresolved, and 0 are actionable (0missing-rule).Every class it used to hold is closed, and the rule table did not change once to close any of them.
entails()reaches a conclusion six ways, and five of the six are not matching:- refutation. A negative fact still has no head anywhere in Tables 4–9 —
no rule concludes
owl:differentFrom, and none concludes membership in anowl:complementOfclass. What the table does have is seventeen rules whose conclusion isfalse, and those seventeen are an inconsistency calculus: assert the conclusion’s negation into the premise, re-run the same seventy-eight rules over a premise whose consistency the first run already established, and read the resulting inconsistency as the proof. Anowl:AllDifferentcollection is, by OWL 2’s own definition, the conjunction of itsn(n−1)/2pairwise inequalities, so it lowers to the same shape and is entailed exactly when every pair refutes — which is why two entries left theschema-conclusionclass with them. - freeze-and-chase.
p rdf:type owl:TransitivePropertyabbreviates a universally quantified Horn implication, and an implication is decided by generalisation on constants: freeze its body over constants the premise does not mention, re-run the table, and look for the head.chain2trans1’s arrives throughprp-spo2, one of the 78. The axiom’s other conjunct —pis an object property — is a lookup in the premise’s own closure, and it is owed: a schema axiom is a conjunction and establishing only the interesting half would claim conclusions the semantics does not license. - comprehension. A conclusion may assert that a CLASS EXISTS — an
anonymous
owl:unionOf, an anonymousowl:Restriction— which the RDF-Based semantics’ own comprehension conditions license, subject to a typing side condition on the operands. Only the scaffolds the conclusion names are minted, over blank nodes checked absent from both documents. - reflexivity.
owl:ReflexivePropertyis outside the RL syntax, so the profile states no rule for it — and a rule that did would range over every resource, widening a closure every consumer computes by default. The conclusion’s own self-loops are read off the premise’s reflexive typings instead. - datatype containment. A property’s declared
rdfs:rangedatatypes intersect, and the intersection may be contained in one the premise never mentions —xsd:byte ⊑ xsd:short, andshort ⊓ unsignedInt ⊑ unsignedShort, neither of which a join over triples can discover. Decided over the XSD value spaces, three-valued, with the negative answer gated on the counterexample range being exactly decided.
The last case needed no mechanism at all, only the document its premise names:
webont-imports-011owl:importsa support ontology the upstream manifest does not inline, so it is vendored beside the cases from W3C’s own URL and supplied toentails()as caller-owned configuration. The library still fetches nothing.Nothing about the inventory moves:
rules(Regime::OwlRl)andimplemented(Regime::OwlRl)are still exactly the same 78,extensions(Regime::OwlRl)is still the oneext-eq-diff-sym, and strictMaterialization::OwlRloutput is byte-for-byte what it was. The evidence moves instead — each mechanism arrives with its ownEntailmentWarrantarm carrying what it actually used (thefalse-concluding rule that fired and a minimal entailing premise subset; the frozen constants, body and head; the minted triples and the closure triples that license them) and its own checker that re-decides the whole thing without running a reasoner.The one case that used to be actionable is closed by an extension, and the extension is labelled rather than absorbed.
a owl:differentFrom bentailsb owl:differentFrom a, which is sound —owl:differentFromdenotes inequality and inequality is symmetric — and shaped exactly likeprp-symp, yet is not among the 78 rules, because Table 4’sowl:differentFromrules only ever concludefalse. PurRDF states it asext-eq-diff-sym, in a rule family declared to sit outside every specification table:extensions(Regime::OwlRl)returns it,rules()andimplemented()are still exactly the same 78 and return none of it,RuleId::is_extensiondecides which is which, and every rendered report carries anextension ext-eq-diff-symline beside itsmissinglines. So the closure a caller gets is Tables 4–9 plus a list it can read and reject, andOWL-RL 78 / 78remains a claim about Tables 4–9 and nothing else. - refutation. A negative fact still has no head anywhere in Tables 4–9 —
no rule concludes
The live scoreboard is
docs/CONFORMANCE.md.