- Status: implemented ·
lean/OOCert/Horn.leanwithhorn_certificate_soundmachine-checked ·lean/OOCert/HornBuiltin.leandischarges the engine's Horn rules against the semantics (27 table rows covering 25 W3C rules, sincescm-eqc1andscm-eqp1each license two conclusions), givingentails_of_builtin_horn· checkeroo-horn, gated bytests/lean_horn_certificate_test.rsand a CI leg ·reason --rulesemits Horn certificates, andlean/OOCert/Mixed.leanhas landed - Written: 2026-09-14
- Related: decision 0002 (an inference carries a certificate); the adversarial-SHACL assurance laundering work, which named the failure this decision is built to avoid
checkStep had one arm per rule and Soundness.lean one lemma per arm. That is fine for a fixed
set of twenty and impossible for rules a user writes, and rules a user writes are the entire
logic-programming family: RIF Core, Datalog, SWRL, and any domain rule table a customer has.
Extending the checker rule by rule would mean a Lean edit per customer.
- Rules become data. A
RulePatternis a body and a head over triple patterns with variables. A certificate step cites a rule by index into a supplied table and gives a binding.checkHornStepverifies the binding instantiates the body into known triples and the head into the claimed conclusion. One generic arm replaces the per-rule ones. - One theorem covers every rule table at once.
horn_certificate_soundneeds no case analysis over rules, because there is no per-rule structure left in the checker. Axioms arepropext,Classical.choice,Quot.sound, identical to the existing theorem. The trust surface does not grow. - The built-ins are a rule table like any other. Most of the engine's rules are Horn rules and are
written out as data in
HornBuiltin.lean, each with a one-line lemma deriving it from the semantic conditions.cls-int1andcls-uniare not Horn rules and stay as arms: their premise count is the length of an RDF list, which is data rather than fixed by the rule. That is not a gap in the generalisation, it is the exact boundary of the Horn family, and a user rule language will stop in the same place. - The two verdicts never share a word. A certificate over the built-ins yields
Entails: true in every model of the asserted graph. A certificate over user rules yieldsEntailsR: true in every model that ALSO satisfies those rules. The rules are assumed and never checked, so a rule reading "every supplier is compliant" produces certificates that check green for ever. The checker printsentailedorentailed_under_supplied_rules, names a different theorem for each, and carries a digest of the table that was in force.a_user_rule_never_earns_the_absolute_verdictfails if that distinction is ever lost. Without it this layer is a machine for turning an assumption into a fact with a proof attached, which is the thing this project named and then had to find inside its own Lean layer once already. - The digest identifies, it does not commit.
String.hashis not a cryptographic hash. It exists so two reports can be compared, not so one can be defended against someone who controls the rule file. Anyone needing that should hash the file.
The checker says a conclusion follows from the premises by the cited rule. It says nothing about whether the rule is true, whether the rule set is consistent, or whether the asserted graph is accurate. Under a user table the guarantee is conditional on assumptions the user wrote, and the report says so in the verdict rather than in a footnote.
Both items listed here as not done have landed. reason --rules evaluates a supplied table and
emits a certificate, and Mixed.lean lets one certificate carry both built-in arms and Horn steps,
which needed Soundness.lean generalised from Entails G to EntailsIn P across about twenty
cases. The engine's rule count has since grown; the checker covers 29 of the OWL 2 RL profile's 78
rules, 30 counting cax-dw in the refutation layer, and four of them stay as hand-written arms
because their premise count is the length of an RDF list.
This decision said the logic-programming family — RIF Core, Datalog, SWRL — was the reason to make
rules data. For a while that was true of the checker and false of the product: the only rule syntax
anything here could read was rules.tsv, which is an internal encoding and not a language anybody
writes in, and grep found no mention of SWRL or RIF outside this file. A user holding a rules file
in a standard syntax could use none of it.
src/rulesyntax.rs and rules-import / onto_rules_import close that. SWRL is read out of a
loaded RDF graph through the swrl:Imp encoding, reusing the one rdf:first/rdf:rest reader the
DL parser already had; RIF Core is read out of its normative XML syntax. Datalog is deliberately NOT
offered as a front end, because it has no single standard concrete syntax and a Datalog program over
triples IS a rules.tsv table.
Three things follow from decision 4 and are not negotiable in that code:
- Both front ends produce USER tables. Every rule is named
swrl/…orrif/…, no built-in rule is, andoo-hornawardsentailedonly to a table that renders identically to the built-in one. So an imported table structurally cannot earn the absolute verdict.a_swrl_rule_never_earns_the_absolute_verdictand its RIF twin are the front ends' copies ofa_user_rule_never_earns_the_absolute_verdict, and they run the whole pipeline intooo-horn. - A rule that cannot be represented is refused by name and counted. Both languages exceed a
Horn table: SWRL has built-in atoms, same- and different-individual atoms and data ranges; RIF
Core has equality, External,
Expr,rif:localand list terms. By default ONE refusal fails the whole import and no table is written, because a rule set that quietly lost half its rules still reaches a fixpoint and its certificate still checks green — a sound proof about a rule set nobody wrote, which is the laundering failure in its purest form.allow_partialis the opt-in and setscertifies_a_weaker_rule_set, the name the DL model-certificate block already uses for the same idea. - The supported fragment is stated, never implied.
docs/rule-syntax-front-ends.mdand every response carry it. "SWRL is supported" would be the false claim this decision exists to prevent one level up.
Nothing inLanded.src/writes the refutation format, so inconsistency can be checked and not yet produced by this engine.reason --certificate DIRnow looks for a contradiction in the closure it reached and writesrefutation.tsvfor the one clash rule the checker has a semantic condition for,cax-dw. Nine further clash rules are detected and deliberately NOT written, because a file naming a ruleOOCert.RefuteConditionshas no field for is refused with exit 2 and would be a certificate-shaped object that certifies nothing. See lean-certificates.md.- The tableau's unsatisfiability findings still carry no refutation.
oo-refute/1can express one contradiction,cax-dw, over triples the forward-chaining prefix can reach; a clash the tableau reaches through∃/∀expansion or a cardinality bound is not expressible in it, and giving it a form would be a change tolean/. The precise obstruction is written down rather than left as "future work". - The RIF presentation syntax is not parsed. It is refused by name; a half-written parser for it would mis-read rules rather than refuse them.