Theorem registry#

Deliverable 1 of goal G1 in designissues/2026-08-05-semantics-proposal-adoption.md. Deliverable 2 (issue #403, the curated review kernel — the minimal subset a W3C-domain expert can read end to end, with the guarantee nothing outside it overrides what it states) is review-kernel.md. G1's aim: a W3C-domain expert can review Factoidal's semantics — the table rows, the spec predicates that transcribe them, and this registry's status column — and know the reviewed definitions are not overridden by implementation detail, without reading a single F* proof. The proofs' only job is the "not overridden" guarantee, checked by make verify-rdf-mt (20 modules, listed under Trust surface below).

Maintenance: hand-curated. There is no generator. Update this file in the same commit as any proof landing that changes a status cell — a new _licensed/_sound/_true lemma, a STOP/impossibility finding, or a ledger-drift correction. A stale registry is worse than none: it is exactly the "reviewed definitions overridden by implementation detail" failure G1 exists to prevent.

Date: 2026-08-06 (status cells for cls-hv1, cls-hv2, cls-avf and prp-spo2 n=2 refreshed twice by the wave 3 relift and wave 4 truth landings; cax-adc, prp-adp, eq-diff2, eq-diff3, cls-maxqc1 clash rows adjudicated and cax-adc's detection-soundness lemma landed by the CLASH-ROW landing). Tree state: commit f70ed89 plus the CLASH-ROW working tree (uncommitted at write time; the orchestrator gates — OWL.Closure.fsti is extraction-affecting). The original survey was read-only at 27c23d5; since then the relift and CLASH-ROW landings edited OWL.Closure.fsti (behavior-identical lambda/boolean lifting only — PROOF-FRIENDLY GUARD RULE banners), and wave 4 added three semantic conditions + Rules 33-36 with no engine edits.

Scope, honestly stated: this registry covers the OWL 2 RL/RDF rule-by-rule licensing + truth-preservation program (OWL.RL.Spec.fst / OWL.RL.Refinement.fst / OWL.Semantics.Soundness.fst) and the RDF / RDFS / Simple entailment rungs (RDF.Entailment.*.fst). It does NOT cover: SHACL, RDFC-1.0 canonicalization, SPARQL algebra/evaluation correctness, the parser layer, or the storage/COTTAS backend — those have their own test suites (skills/test-suites/SKILL.md) but no model-theoretic proof program yet. Counts below are always N of M, both labelled.

1. OWL 2 RL/RDF — licensing and truth, per engine rule#

The 84-function engine ledger (foot of ../formal/fstar/OWL.RL.Spec.fst) classifies every OWL.Closure rule function as [row] (implements a named W3C table row), [ext] (sound extension, justified in the rule's own banner — no table row exists to license against), [axm] (materialises an axiomatic-triple table, not a rule), or [mode] (fires only under a catalog semantics mode). This table is that ledger, plus two proof columns:

Status values: PROVED, PARKED(reason), IMPOSSIBLE(evidence), UNATTEMPTED, N/A (proof kind does not apply to this entry class).

Licensing count: 26 of 34 [row] table-row obligations proved (2026-08-06). The figure was 23 at commit 27c23d5; wave 3 added cls-hv2 (commit 06f3f20, against the weakened row cls_hv2_derives_approx), then cls-hv1 and cls-avf in the relift landing. prp-spo2 is NOT counted: its row is shared between property_chain_2 and property_chain_n, and only the n=2 half is proved — the row becomes coverable when property_chain_n follows. The rest of this paragraph derives the 23 baseline, using the ledger's own accounting (subprop_domain_range and cls_maxqc34 each count as covering TWO rows; property_chain_2/ property_chain_n count as covering ONE row between them — the arithmetic the ledger's closing comment states). This matches the count commit 27c23d5 itself reports ("Licensing count: 23 of 34 rows proved"). Verified independently for this registry: counting owl_rule_*_licensed lemmas directly in OWL.RL.Refinement.fst gives 22 lemmas; two of them (inverse_of, subprop_domain_range) each license TWO distinct _derives predicates in one lemma. Taking inverse_of's pair as counting ONE unit (the convention the ledger's "34" arithmetic implies, symmetric with how property_chain's two functions collapse to one row) reproduces 23 exactly. Taking it as counting two units (the literal reading — Table 4 lists prp-inv1 and prp-inv2 as two separate rows with distinct premises) gives 24. Discrepancy flagged, not resolved: the "34" denominator's footnote only names subprop_domain_range and cls_maxqc34 as double-counted; it does not say inverse_of is single-counted despite covering two literal table rows the same way subprop_domain_range does. Someone should either adjudicate this explicitly in the ledger comment or accept the count is 23-or-24 depending on convention.

CLASH-ROW adjudication landed 2026-08-06 (does not change the counts above — clash rows are [row]-classed but their proof kind is DETECTION SOUNDNESS, not the derives-style "every emission is licensed" statement the 26-of-34 count above measures). The registry flagged five clash-predicate rows (cax-adc, prp-adp, eq-diff2, eq-diff3, cls-maxqc1) as "truth column N/A pending domain review" since creation. Adjudicated: the engine's only consumer of any *_clash predicate is OWL.Closure.is_inconsistent, called on the RL closure fixpoint (bin/owl-runner/owl_runner.ml's capped_is_inconsistent, which is how every ConsistencyTest / InconsistencyTest is actually scored) — so "the engine reports a clash" concretely means one of is_inconsistent's internal boolean checks evaluates true, and the natural licensing statement is DETECTION SOUNDNESS (that boolean == true ==> the clash predicate(s) it corresponds to hold of g); the converse (detection COMPLETENESS) is a separate, per-row-noted statement. Two checks (is_inconsistent 3 and 6, now named owl_has_disjoint_class_clash / owl_has_pdw_direct_clash — hoisted out of anonymous let-bindings, behavior-identical, OWL.Closure.fsti) turn out to be shared with an EARLIER table's asserted-form sibling row (cax-dw, prp-pdw respectively) — the boolean cannot tell whether the clash-causing triple was asserted directly or materialised from an AllDisjointClasses/AllDisjointProperties list, so the only TRUE statement is the row UNION, not the single [row] alone. cax-adc's union statement is PROVED (theorem_cax_adc_cax_dw_detection_sound, OWL.RL.Refinement.fst section 30); prp-adp and eq-diff2/eq-diff3 are PARKED with a precise obstruction recorded per row (a literal value-vs-syntactic-equality gap, and a sameAs-direction/symmetry gap, respectively); cls-maxqc1 is a confirmed engine GAP — no detector exists for the row at all. Full reasoning in each row's Notes cell below.

Truth count: 33 of ~36 attempted rules proved (wave 4, Rules 33-36, landed 2026-08-06: cls-hv1, cls-hv2, cls-avf, prp-spo2 n=2) (OWL.Semantics.Soundness.fst "Rules 1-36" banner numbering; two banner slots, 2b and 16→17, are a sub-rule and a superseded first attempt, so 31 banner numbers cover 30 distinct engine rules); 3 of those 30 are IMPOSSIBLE with recorded evidence (fresh-bnode-minting rules); the remaining ~54 of 84 ledger entries are UNATTEMPTED for truth (no _sound lemma exists yet).

G3 M4 wave 1 (2026-08-06) landed Rules 21-27: scm_eqp2, sameAs_ transitivity, transitive_property, functional, inverse_functional, inverse_of, and prp_key — all seven target rules PROVED, including prp-key against the UNWEAKENED cond_haskey condition (the bridging fact the registry asked for, cond_literal_term_eq_respecting + lemma_rdf_term_eq_denot, was proved rather than needed as a park). Six new semantic conditions landed in OWL.Semantics.fst: cond_mutual_subproperty_equivalent, cond_transitive, cond_functional, cond_inverse_of, cond_literal_term_eq_respecting, cond_haskey; a seventh, cond_inverse_functional, landed in OWL.Semantics.Soundness.fst next to Rule 25 (mirrors cond_functional, symmetric in which side of the pair is fixed). G3 M4 wave 4 (2026-08-06) landed Rules 33-36 — the truth-preservation proofs for the four rules whose LICENSING landed earlier the same day in the wave 3 relift (cls-hv1, cls-hv2, cls-avf, prp-spo2 n=2), all four PROVED first attempt against the wave 3 relift's named top-level engine helpers. Three new semantic conditions landed in OWL.Semantics.fst: cond_hasvalue (Table 8 HasValue, stated as the full iff — cls-hv1 reads the forward direction, cls-hv2 the backward one, the same two-rule iff split cond_inverse_of already serves for prp-inv1/prp-inv2), cond_allvaluesfrom (Table 8 AllValuesFrom, one direction — the table's own condition is already one-directional, no converse row exists the way cls-hv2 converses cls-hv1), and cond_chain2_compose (Table 5's general 2-hop SubObjectPropertyOf(ObjectPropertyChain(P1,P2),Q) composition condition, distinct from Rule 11's cond_chain2_transitive, which specialises the SAME table row to the self-composition case Q=P1=P2=P). cls-hv1/cls-avf/prp-spo2 followed the DEPTH COROLLARY (proof-factory skill): one standalone witness-assembly lemma per fold level, referencing the wave 3 relift's named engine helpers directly (owl_cls_hv1_outer/_mid/_emit, owl_cls_avf1_outer/_prop/ _member/_emit, owl_chain2_outer/_mid/_emit), no closure- identity risk at any level. cls-hv2's engine function was NOT lambda-lifted in the relift (its own licensing proof already discharges with anonymous local lambdas), so its truth proof mirrors that proof's local-verbatim-lambda skeleton instead, with only the deepest semantic-assembly step pulled into a standalone lemma. cls-hv2 proves UNWEAKENED (no _approx predicate needed for truth, unlike its licensing statement): the same cond_literal_term_eq_respecting + lemma_rdf_term_eq_denot bridge that closed prp-key's engine-vs-row gap (wave 1) closes cls-hv2's rdf_term_eq-vs-== gap here too. prp-spo2 (n=2) reuses decode_chain_pair_sound (Rule 11) VERBATIM for the list-decode half, per the BRIDGE-LEMMA COROLLARY, rather than re-deriving a new list-walk bridge. prp-spo2 (n≥3) (property_chain_ n) remains UNATTEMPTED for both licensing and truth — out of scope for this landing.

W3C id / marker Spec predicate Engine function Licensing Truth Hypotheses / fragment notes Notes
scm-eqc1 scm_eqc1_derives (OWL.RL.Spec.fst) equivalent_class ✅ PROVED ✅ PROVED none beyond holds_all/graph membership Ledger-drift #1 (commit 7ba8bb1): entry previously claimed cax-eqc1/eqc2; corrected to scm-eqc1 while writing the licensing lemma.
scm-eqp1 scm_eqp1_derives equivalent_property ✅ PROVED ✅ PROVED none Ledger-drift #2 (commit 78e9c0c): previously claimed prp-eqp1/eqp2; same disease as scm-eqc1.
scm-eqc2 scm_eqc2_derives scm_eqc2 ✅ PROVED ✅ PROVED ig_wf_sp, ig.ig_triples == g Truth proof needed a guard-depth flattening (2026-08-05) after a first attempt hit depth-4 SMT witness chains (Rule 16 in Soundness banner records the parked first attempt; Rule 17 is the pass).
scm-eqp2 scm_eqp2_derives scm_eqp2 ✅ PROVED ✅ PROVED ig_wf_sp, ig.ig_triples == g, cond_mutual_subproperty_equivalent Rule 21 (Soundness banner); mirrors Rule 17's guard-depth-flattened scm-eqc2 proof exactly, one level down (properties instead of classes).
prp-symp prp_symp_derives symmetric_property ✅ PROVED ✅ PROVED none —
prp-trp prp_trp_derives transitive_property ✅ PROVED ✅ PROVED ig_wf_sp, snapshot semantics, cond_transitive Rule 23 (Soundness banner); NESTED-SINGLE, same collect-then-emit shape as Rule 1 (prp-symp) one indexed-lookup deeper.
[ext] n/a — no table row; banner in OWL.Closure.fsti inverseOf_domain_range_flip N/A UNATTEMPTED — Domain/range of P become range/domain of P⁻¹.
prp-inv1 + prp-inv2 prp_inv1_derives / prp_inv2_derives inverse_of ✅ PROVED (both, one lemma) ✅ PROVED (both, one lemma) cond_inverse_of (iff, symmetric in which direction fires) Licensing: solved via the closure-identity / PROOF-FRIENDLY GUARD RULE fix (OWL.Closure.inverse_of_emit, task #36, commit fb8d98f) — first rule proved after the nested-pair closure-identity obstruction was diagnosed. Truth: Rule 26 (Soundness banner), reuses the same named inverse_of_emit symbol so the nested-pair step obligation carries no fresh closure-identity risk.
eq-ref eq_ref_derives sameAs_reflexivity ✅ PROVED ✅ PROVED none —
eq-sym eq_sym_derives sameAs_symmetry ✅ PROVED ✅ PROVED none First licensing lemma landed in the program.
[ext] n/a differentFrom_symmetry N/A ✅ PROVED none Table 5.13's differentFrom condition is symmetric in its arguments; first [ext] entry to get a truth proof.
eq-trans eq_trans_derives sameAs_transitivity ✅ PROVED ✅ PROVED ig_wf_sp, cond_sameas_identity Rule 22 (Soundness banner); carried entirely by cond_sameas_identity's iff (both directions), no dedicated transitivity condition needed.
eq-rep-s eq_rep_s_derives sameAs_replace_subject ✅ PROVED ✅ PROVED ig_wf_subj —
eq-rep-o eq_rep_o_derives sameAs_replace_object ✅ PROVED ✅ PROVED ig_wf_obj —
eq-rep-p eq_rep_p_derives sameAs_replace_predicate ✅ PROVED ✅ PROVED ig_wf_pred —
prp-fp prp_fp_derives functional ✅ PROVED (commit 27c23d5) ✅ PROVED needs named top-level helpers (owl_prp_fp_collect_step/emit/step) — lambda-lift landed same commit; truth adds cond_functional, cond_sameas_identity Rule 24 (Soundness banner); references the engine's own named helpers directly — cleanest of the G3 M4 wave 1 rules, no closure-identity risk at any fold level.
prp-ifp prp_ifp_derives inverse_functional ✅ PROVED (commit 27c23d5) ✅ PROVED ig_wf_po, ig_wf_pred, graph_literal_match_exact; needed RDF.Indexed.fsti helper naming (triple_obj_matches, triple_subject_of); truth adds cond_inverse_functional, cond_sameas_identity Closure-identity law applies one level down through find_subjects_indexed's filter/map, not just the emit fold. Truth: Rule 25 (Soundness banner) reuses the licensing proof's lemma_find_subjects_indexed_wf verbatim via open OWL.RL.Refinement in OWL.Semantics.Soundness.fst (new cross-file dependency, no cycle).
[ext] n/a pdw_to_differentFrom N/A UNATTEMPTED — Disjoint properties force distinct subjects on a shared object.
[ext] n/a pdw_shared_value_to_differentFrom N/A UNATTEMPTED — Sibling of the above.
[ext] n/a fp_diff_to_diff N/A UNATTEMPTED — Functional property + different objects force different subjects.
[ext] n/a ifp_diff_to_diff N/A UNATTEMPTED — Inverse-functional mirror.
[ext] n/a disjoint_with_propagation N/A ✅ PROVED none Disjointness inherits down rdfs:subClassOf (cites cax-dw + cax-sco); second [ext] truth proof landed.
[ext] n/a disjoint_to_complement N/A UNATTEMPTED — disjointWith into complementOf scaffolding for the clash checker.
[ext] n/a svf2_existential_witness N/A UNATTEMPTED — Comprehension-witness layer (four nested folds).
[ext] n/a minc1_bridge N/A UNATTEMPTED — Comprehension-witness layer.
cls-hv1 cls_hv1_derives cls_hv1 ✅ PROVED (2026-08-06, wave 3 relift) ✅ PROVED (2026-08-06, G3 M4 wave 4) ig_wf_sp, ig_wf_po, ig.ig_triples == g; needed named top-level helpers (owl_cls_hv1_outer/_mid/_emit); truth adds cond_hasvalue (forward direction) First THREE-level fold rule proved. Four earlier attempts failed Error-19 on the tu eliminate with the engine's three lambdas still anonymous; the closure-identity law is what bit. Fix = lambda-lift ALL three engine levels + package each proof level as a standalone lemma taking the level above's witnesses as arguments (lemma_cls_hv1_row_intro), so the row's four-way existential is assembled in one flat context. Section 26. Truth: Rule 33 (Soundness banner); same named-helper reuse + one standalone witness lemma per level (lemma_cls_hv1_witness_holds) as the licensing sibling, first-attempt green.
cls-hv2 cls_hv2_derives cls_hv2 ✅ PROVED against cls_hv2_derives_approx (commit 06f3f20) ✅ PROVED, UNWEAKENED (2026-08-06, G3 M4 wave 4) ig_wf_sp, ig_wf_po, ig_wf_pred, ig.ig_triples == g; truth adds cond_hasvalue (backward direction) + cond_literal_term_eq_respecting WEAKENED ROW (same convention as prp-key): the engine's find_subjects_indexed ig p v falls back to an rdf_term_eq filter when v is a literal, which is coarser than the row's == (lang-tag case folding, XMLLiteral c14n). Machine-checked witness in section 27; row transcription itself is faithful, this is a confirmed engine-vs-row narrowing, not a ledger drift. Truth: Rule 34 (Soundness banner) closes the gap the SAME way prp-key's truth proof did — cond_literal_term_eq_respecting + lemma_rdf_term_eq_denot show rdf_term_eq-equal terms co-denote, so the proof goes through UNWEAKENED even though the licensing statement stays weakened. Engine not lambda-lifted (only cls-hv1/cls-avf/prp-spo2 were); truth proof mirrors the licensing proof's local-verbatim-lambda skeleton, with the deepest semantic-assembly step pulled into its own lemma (lemma_cls_hv2_witness_holds) per the depth corollary.
[ext] n/a cls_svf2_qualified N/A UNATTEMPTED — Comprehension layer.
[ext] n/a cls_minc_qual1 N/A UNATTEMPTED — Comprehension layer.
[ext] n/a cls_hasself1 N/A UNATTEMPTED — ObjectHasSelf semantics (Table 5.x); hasSelf has no RL table row.
[ext] n/a cls_hasself2_synth N/A ❌ IMPOSSIBLE (mints a fresh bnode; Rule 15 in Soundness banner) — Same STOP shape as transitive_to_chain/cls_svf_thing_materialize: a comprehension-witness existence condition the W3C tables do not assert, under the fixed-assignment shape.
[ext] n/a cls_svf_thing_materialize N/A ❌ IMPOSSIBLE (mints a fresh bnode; Rule 14) — Mints canonical_svf_thing_restriction_bnode; no premise in g asserts that resource already exists. Closing it needs a model-EXTENSION lemma (Henkin/Skolem-style), a different lemma shape than Rules 1-11/13 use.
[ext] n/a cls_svf_thing_witness N/A UNATTEMPTED — Comprehension layer.
[ext] n/a cax_dw_to_differentFrom N/A UNATTEMPTED — Disjoint classes force differentFrom on their members.
cls-maxqc1 (clash) cls_maxqc1_clash NONE (no engine detector) GAP — no detection event exists to license N/A (clash/inconsistency row, not derivation) — CLASH-ROW ADJUDICATION (2026-08-06): grepping cls_maxqc1_clash outside OWL.RL.Spec.fst finds nothing — no is_inconsistent arm, no other engine function, checks the "?x owl:maxQualifiedCardinality 0 ... plus a witness satisfying the forbidden class" pattern. OWL.Closure.owl_rule_cls_maxqc1 is a SAME-NAMED but UNRELATED rule: per its own header (OWL.Closure.fsti ~2011-2106) it materialises owl:maxQualifiedCardinality "1" canonicals for parent7/parent8 SPARQL-entailment query answering, not a "0"-cardinality clash check. This is a genuine engine GAP against the row, not a proof-scoping decision — not attempted, since there is no boolean to state a lemma about. Needs a NEW is_inconsistent arm before a licensing lemma is meaningful.
[ext] n/a cls_exactqc1 N/A UNATTEMPTED — Exact qualified cardinality decomposes into min+max; no RL row of its own.
cls-maxc2 cls_maxc2_derives cls_maxc2 UNATTEMPTED UNATTEMPTED — —
[ext] n/a cls_maxqc_comp N/A UNATTEMPTED — The #236 anchor machinery. Known sound-but-narrow (see CLAUDE.md "Known sound-but-narrow rewrites"): drops vacuous-truth individuals and OWL-Full punned class-individuals; the internal-variable LEAK the 2026-07-09 strict runner found is FIXED (task #100, strip_rewrite_internal_vars).
cls-avf cls_avf_derives cls_avf1 ✅ PROVED (2026-08-06, wave 3 relift) ✅ PROVED (2026-08-06, G3 M4 wave 4) ig_wf_sp, ig_wf_po, ig.ig_triples == g; needed named top-level helpers (owl_cls_avf1_outer/_prop/_member/_emit); truth adds cond_allvaluesfrom The program's deepest rule — FOUR fold levels. Same two-part treatment as cls-hv1: lambda-lift every engine level, then one standalone lemma per proof level with the level above's witnesses as arguments (lemma_cls_avf_row_intro assembles the six-way existential flat). Section 29. Truth: Rule 35 (Soundness banner); lemma_cls_avf_witness_holds assembles the row's six-way existential flat, reusing lemma_find_subjects_indexed_wf_subj a second time in the same proof; first-attempt green.
[ext] n/a reflexive_property N/A UNATTEMPTED — ReflexiveProperty semantics; RL profile has no prp-rfl row.
[ext] n/a scm_cls_restriction N/A ✅ PROVED none, single-fold, no fresh bnodes owl:Restriction rdfs:subClassOf owl:Class (Table 5, Axiomatic Triples) read through the RDFS class-extension condition.
prp-spo2 (n=2) prp_spo2_derives property_chain_2 ✅ PROVED (2026-08-06, wave 3 relift) ✅ PROVED (2026-08-06, G3 M4 wave 4) ig_wf_sp, ig.ig_triples == g; needed named top-level helpers (owl_chain2_outer/_mid/_emit); truth adds cond_chain2_compose Splits one row across two engine functions by chain arity. The list bridge lemma_decode_chain_pair_licensed is the syntactic twin of decode_chain_pair_sound, rebuilt in section 18's LIST-WALK spelling (served-object equation → bucket memP → ig_wf_sp-pinned triple → record-literal memP _ g); the parked attempt collapsed those four steps into one assert and Z3 could not e-match ig_wf_sp for two buckets at one node. Section 28. Truth: Rule 36 (Soundness banner); reuses decode_chain_pair_sound (Rule 11) VERBATIM for the list-decode half per the bridge-lemma corollary, and cond_chain2_compose (the GENERAL 2-hop composition condition, distinct from Rule 11's self-composition-only cond_chain2_transitive) for the semantic half; first-attempt green.
prp-spo2 (n≥3) prp_spo2_derives property_chain_n UNATTEMPTED UNATTEMPTED — See above.
[ext] n/a chain_to_transitive N/A ✅ PROVED no recursion needed (unlike Rule 4's list-walk) A chain p·p → p IS transitivity; bridge, banner proof.
[ext] n/a transitive_to_chain N/A ❌ IMPOSSIBLE (mints two fresh bnodes; Rule 12) — Converse bridge. Needs canonical_chainl1_bnode/_chainl2_bnode witnesses (rdf:first/rdf:rest list cells) with no premise in g requiring their existence — an existence condition, not an implication, that no W3C RDF-Based table asserts. STOP per the two-attempt rule; would need a model-extension lemma or a licensed-by-g (syntactic-provenance) reframing instead.
[ext] n/a named_sameAs_to_equivClass N/A UNATTEMPTED — RDF-Based semantics: sameAs classes are equivalentClass (Table 5.x).
[mode] n/a named_equivClass_to_sameAs_mode N/A UNATTEMPTED — Converse, catalog-gated (owl_semantics_direct).
[mode] n/a named_equivClass_to_sameAs N/A UNATTEMPTED — Wrapper of the above.
cls-int2 cls_int2_derives cls_int1 ✅ PROVED (today's precursor, commit 2e482d3) ✅ PROVED (Rule 31) list-walk bridge (decode_iri_list_sound) MISNAMED function: reads x typed into intersection C, emits x typed into every member Cᵢ — cls-int2's shape, not cls-int1's (which synthesises C-membership from a batched premise). No engine function realises the literal cls-int1 row. Corrected 2026-08-05 (ledger drift #5/#6, commit 72a965c).
cls-oo cls_oo_derives cls_oneof ✅ PROVED ✅ PROVED list-walk bridge —
scm-uni (Table 8) scm_uni_derives cls_uni ✅ PROVED (today's precursor, commit 2e482d3) ✅ PROVED (Rule 32) list-walk bridge MISNAMED function: emits Cᵢ rdfs:subClassOf C per member — Table 8's schema row, not Table 5's cls-uni type-propagation row. The owl:disjointUnionOf branch additionally emits an EXTENSION (plain-unionOf restatement + pairwise disjointWith), no W3C row of its own. Corrected 2026-08-05 (commit 72a965c).
[ext] n/a cls_uni_elim N/A UNATTEMPTED — Union-membership elimination under disjointness side conditions.
[ext] n/a oneof_set_equivalence N/A UNATTEMPTED — oneOf lists with equal member sets name equivalent classes.
prp-key prp_key_derives (row) / prp_key_derives_approx (proved against) prp_key ✅ PROVED, WEAKENED ROW (commit c600646) ✅ PROVED, UNWEAKENED ig_wf_sp, ig.ig_triples == g, cond_haskey, cond_sameas_identity, cond_literal_term_eq_respecting Engine's agree_on_property uses rdf_term_eq (RDF-1.1 value equality — case-insensitive lang tags, XMLLiteral c14n, #337) where the row's shares_key_values uses plain ==. Machine-checked counterexample ("Alice"@en vs "Alice"@EN) shows the engine accepts strictly MORE value pairs as "shared" than the literal row licenses — an OVER-approximation on the value-sharing axis (opposite direction from the cls-int/scm-uni narrowing above). owl_rule_prp_key_licensed is proved against the local weakening, not prp_key_derives itself. WEAKENED-ROW CONFIRMATION, not a ledger drift — the row transcription is faithful to Table 4. Truth closes the gap the licensing weakening left open (G3 M4 wave 1, 2026-08-06): cond_literal_term_eq_respecting + lemma_rdf_term_eq_denot (OWL.Semantics.fst) establish that rdf_term_eq-equal literals denote the SAME domain element under any genuine interpretation — RDF 1.1 Concepts §3.3 already treats case-different-but-equal language tags as the SAME abstract literal term, not two co-denoting ones — so the engine's "extra" accepted pairs are one value read through two spellings, not a semantic overreach. Rule 27 (Soundness banner) is proved against the UNWEAKENED cond_haskey (no local weakening needed on the truth side).
[axm] n/a xsd_datatype_axioms N/A N/A (axiomatic table, not a rule) — dt-type1 instantiated at supported XSD datatypes.
[ext] n/a dt_range_intersect N/A UNATTEMPTED — Two ranges compose to their intersection (WebOnt-I5.24-002); explicitly NOT a minimality claim.
scm-dom1 scm_dom1_derives scm_dom2 ✅ PROVED (commit 276ee77) ✅ PROVED (Rule 28) — MISNAMED function: lifts domain up subClassOf — scm-dom1's work, refuted-by-counterexample against scm-dom2 (whose subPropertyOf logic lives in subprop_domain_range below). Third ledger misclassification, caught by counterexample (commit 276ee77).
scm-rng1 scm_rng1_derives scm_rng2 ✅ PROVED (commit 276ee77) ✅ PROVED (Rule 29) — Same story as scm_dom2 above, mirrored for range.
scm-dom2 + scm-rng2 scm_dom2_derives / scm_rng2_derives subprop_domain_range ✅ PROVED (both, one lemma, commit 16caed4) ✅ PROVED (Rule 30) ig_wf_sp, ig.ig_triples == g This is the function that actually realises the genuine scm-dom2/scm-rng2 rows (subPropertyOf-lifting) — a THIRD name distinct from the misleadingly-named scm_dom2/scm_rng2 functions above. Two sequential inner folds per outer item (new fold shape for the module).
[ext] n/a symmetric_metapredicates N/A ✅ PROVED none Group E(a): OWL metapredicates whose semantic condition is argument-symmetric (complementOf, disjointWith, inverseOf, …).
[ext] n/a inverse_characteristics N/A UNATTEMPTED — Characteristics transfer across owl:inverseOf.
[ext] n/a equivalent_property_characteristics N/A UNATTEMPTED — Characteristics transfer across equivalentProperty.
[ext] n/a cardinality_to_min_max N/A UNATTEMPTED — owl:cardinality decomposes into min+max (OWL 2 structural equivalence).
cls-maxqc3 + cls-maxqc4 cls_maxqc3_derives / cls_maxqc4_derives cls_maxqc34 UNATTEMPTED UNATTEMPTED — One function realises two rows (like subprop_domain_range).
[ext] n/a fp_pinned_subproperty N/A UNATTEMPTED — Banner proof (referenced by the prp-fp closure-identity fix).
[ext] n/a singleton_nominal_functionality N/A UNATTEMPTED — Banner proof.
[ext] n/a hasvalue_card_disjoint N/A UNATTEMPTED — Banner proof.
[ext] n/a avf_thing_to_range N/A UNATTEMPTED — avf under owl:Thing IS a range fact (WebOnt-I5.24-004).
[ext] n/a extensional_symmetry N/A UNATTEMPTED — Group E(h): right-to-left half of Table 5.14; banner carries the pinned-extension argument.
[axm] n/a xsd_core_datatype_axioms N/A N/A — dt-type1 core set.
[axm] n/a builtin_vocabulary_axioms N/A N/A — Built-in vocabulary axioms.
cax-adc cax_adc_clash (union target: table6_clashes = cax_dw_clash \/ cax_adc_clash) all_disjoint_classes (premise expansion) + is_inconsistent check 3, hoisted as owl_has_disjoint_class_clash ✅ PROVED — detection-soundness against table6_clashes, not cax_adc_clash alone (theorem_cax_adc_cax_dw_detection_sound, OWL.RL.Refinement.fst section 30) N/A (clash row) rdf_type_objects_resource g (rdf:type objects are IRI/bnode, never literal — see banner) CLASH-ROW ADJUDICATION (2026-08-06): the engine detects this row in TWO STAGES — owl_rule_all_disjoint_classes materialises pairwise owl:disjointWith from an owl:AllDisjointClasses membership list (premise infra, not itself a clash check), then is_inconsistent's check 3 (now the named top-level owl_has_disjoint_class_clash, hoisted out of the anonymous let, behavior-identical) looks for two rdf:type triples on one subject whose objects are owl:disjointWith. That boolean CANNOT tell whether the disjointWith triple was asserted directly (cax-dw) or materialised from a list (cax-adc) — a graph with only an asserted cax-dw pair and no AllDisjointClasses node also flips it — so the only TRUE statement it licenses is the row UNION table6_clashes, proved here. Completeness (the converse: table6_clashes g ==> owl_has_disjoint_class_clash g == true) is PLAUSIBLE for the cax-dw disjunct (single step) but ENGINE-NARROWED for cax-adc: owl_rule_all_disjoint_classes uses decode_chain_list, an IRI-only rdf:first/rdf:rest decoder that returns None (no-op) on a member list containing a bnode class expression — the same narrowing already documented for owl_rule_property_chain_n/prp-key's list machinery — so completeness is NOT claimed for AllDisjointClasses lists with non-IRI members.
prp-adp prp_adp_clash (union target: prp_pdw_clash \/ prp_adp_clash, both disjuncts of table4_clashes_complete) all_disjoint_properties (premise expansion) + is_inconsistent check 6, hoisted as owl_has_pdw_direct_clash PARKED — detection-soundness attempted, two-attempt-stop; see Notes N/A (clash row) — CLASH-ROW ADJUDICATION (2026-08-06): SAME shape as cax-adc — owl_rule_all_disjoint_properties materialises pairwise owl:propertyDisjointWith, then is_inconsistent check 6 (hoisted as owl_has_pdw_direct_clash/owl_is_pdw_pair, OWL.Closure.fsti, behavior-identical) looks for two triples sharing subject AND object through a disjoint property pair — cannot attribute to prp-pdw (asserted) vs prp-adp (materialised), so the provable statement is the row union, same reasoning as cax-adc. PARKED, not proved: unlike cax-adc's t1.o/t2.o (rdf:type objects, provably IRI/bnode via rdf_type_objects_resource), check 6's shared object t1.o == t2.o (rdf_term_eq t1.o t2.o, required TRUE by the check) ranges over ARBITRARY property values, which legitimately include literals in real OWL RL data (New-Feature-DisjointDataProperties-*). rdf_term_eq on two literals is RDF-1.1 VALUE equality (case-insensitive lang tag, XMLLiteral c14n, #337) while prp_pdw_clash/prp_adp_clash need SYNTACTIC ==; a graph with t1.o = "V"@EN, t2.o = "V"@en flips the check without giving a ==-exact witness. Closing this needs either (a) a graph-level "literals rdf_term_eq-related implies ==-equal" hypothesis (true for realistic non-adversarial data, mirrors prp-key's cond_literal_term_eq_respecting weakening pattern already accepted in this ledger), or (b) restating literal-value-aware prp_pdw_clash/prp_adp_clash variants in OWL.RL.Spec.fst using rdf_term_eq/literal_value_eq instead of == (the cls-hv2/prp-key WEAKENED-ROW precedent). Recipe otherwise identical to cax-adc's proved lemma; the hoisted owl_has_pdw_direct_clash/owl_is_pdw_pair are ready for whichever fix lands. Completeness note: same ENGINE-NARROWED decode_chain_list (IRI-only) caveat as cax-adc.
eq-diff2 / eq-diff3 eq_diff2_clash/eq_diff3_clash (union target would be eq_diff1_clash \/ eq_diff2_clash \/ eq_diff3_clash) allDifferent_to_differentFrom (premise expansion) + is_inconsistent check 2 (differentFrom_in_graph) PARKED — detection-soundness attempted, two-attempt-stop; see Notes N/A (clash row) — CLASH-ROW ADJUDICATION (2026-08-06): owl_rule_allDifferent_to_differentFrom materialises pairwise owl:differentFrom from BOTH owl:members and owl:distinctMembers lists in ONE fold (unioned before decoding), so the engine cannot distinguish eq-diff2 (owl:members) from eq-diff3 (owl:distinctMembers) even in principle — any provable statement is already a eq_diff2_clash \/ eq_diff3_clash disjunction at best, same row-union pattern as cax-adc/prp-adp (also joined by eq_diff1_clash, the asserted-form sibling is_inconsistent check 2 also flags). PARKED, not proved, for a DIFFERENT reason than prp-adp's literal gap: check 2 (t.p = owl:sameAs && differentFrom_in_graph g (subject_to_term t.s) t.o) tests a candidate differentFrom triple against BOTH directions of one FIXED, directional sameAs triple t. When only the "swapped" direction differentFrom witness exists (d relates t.o to t.s, not t.s to t.o), closing eq_diff1_clash's order-rigid u1.s==u2.s /\ u1.o==u2.o needs a REVERSE sameAs edge (t.o sameAs t.s) that isn't among the two extracted witnesses — it is a fact about g having reached the eq-sym fixpoint (a standard, always-run RL rule at the real call site — is_inconsistent runs on the closure fixpoint output — but not a fact is_inconsistent's own definition carries as a local hypothesis). Recipe for next attempt: add a sameAs_symmetric g hypothesis (mirrors rdf_type_objects_resource/prp-adp's literal hypothesis in kind), or restate against entailment_closure's actual output where eq-sym is known to have fired.
[ext] n/a differentFrom_to_allDifferent N/A UNATTEMPTED — Converse packaging.
[mode] n/a rdf_based_full_meta_axioms_mode N/A UNATTEMPTED — RDF-Based Semantics meta-axioms, catalog-gated.
[ext] n/a comp_singleton_union N/A UNATTEMPTED — Comprehension layer.
[ext] n/a comp_min1_restriction N/A UNATTEMPTED — Comprehension layer.
[ext] n/a comp_oneof_union N/A UNATTEMPTED — Comprehension layer.
[ext] n/a comp_union_oneof N/A UNATTEMPTED — Comprehension layer.
[ext] n/a comp_enum_range_value N/A UNATTEMPTED — Comprehension layer.
[ext] n/a comp_range_avf N/A UNATTEMPTED — Comprehension layer.
[ext] n/a comp_range_intersection N/A UNATTEMPTED — Comprehension layer.
[ext] n/a comp_pinned_domain_enum N/A UNATTEMPTED — Comprehension layer.

Row count: 84 (34 [row], 42 [ext], 4 [axm], 4 [mode], per the ledger's own tally — matches the count transcribed independently above).

2. RDFS entailment (RDF 1.1 Semantics § 9) — 13 rows#

All thirteen rows have spec predicates (RDF.Entailment.RDFS.Spec.fst, rdfsN_derives). 11 of 13 have a genuine shipping engine rule with BOTH licensing (RDF.Entailment.RDFS.Refinement.fst) and truth (RDF.Entailment.RDFS.ModelTheory.fst, rdfsN_true) proved. The remaining 2 rows (rdfs6, rdfs10) have truth proved at the spec-predicate level but no sound engine counterpart: the shipping engine only ships an UNSOUND reflexivity over-approximation for them (finding RS-1, fixed 2026-07-31 by being correctly flagged unsound, not by being replaced). rdfs12 sits in between: truth is proved for the literal row, but the shipping engine's rdfs_rule_container_membership licenses against the axiomatic-triple table instead (it is an unconditional axiom emitter over a finite rdf:_1..rdf:_5 slice, not a premise-driven rule — sound, but not a licensing proof of the rdfs12 row itself).

Row Spec predicate Engine function Licensing Truth Notes
rdfs1 rdfs1_derives rdfs_rule_recognized_datatypes ✅ PROVED ✅ PROVED Added 2026-07-31 closing finding RS-2 (was previously unimplemented).
rdfs2 rdfs2_derives rdfs_rule_domain ✅ PROVED ✅ PROVED Interleaved into the OWL-RL fixpoint too (covers prp-dom).
rdfs3 rdfs3_derives rdfs_rule_range ✅ PROVED ✅ PROVED Finding RS-3: silently drops the literal/triple-term-object case (needs a literal subject, generalized-RDF delta D5); rdfs3_derives carries the same premise, so the refinement is EXACT — the incompleteness is in the term algebra, not the proof. Covers prp-rng in the OWL-RL fixpoint.
rdfs4a rdfs4a_derives rdfs_rule_resource_subject ✅ PROVED ✅ PROVED Added 2026-07-31 closing RS-2.
rdfs4b rdfs4b_derives rdfs_rule_resource_object ✅ PROVED ✅ PROVED Added 2026-07-31 closing RS-2; RS-3 applies here too (literal-object case).
rdfs5 rdfs5_derives (rdfs5_derives2 two-source form) rdfs_rule_subPropertyOf_trans ✅ PROVED ✅ PROVED Two-source (snapshot vs accumulator) split for fixed-point composition.
rdfs6 rdfs6_derives none (only the unsound rdfs_reflexivity_axioms approximation) N/A — no sound engine rule ✅ PROVED (spec-predicate level) RS-1: rdfs_reflexivity_axioms emits P rdfs:subPropertyOf P for every P typed owl:ObjectProperty/owl:DatatypeProperty — NOT RDFS-entailed (an RDFS interpretation may read owl:Class/owl:ObjectProperty as an arbitrary IRI). Machine-checked witness: owl_reflexivity_axioms_not_rdfs_sound.
rdfs7 rdfs7_derives rdfs_rule_subPropertyOf ✅ PROVED ✅ PROVED Covers prp-spo1 in the OWL-RL fixpoint.
rdfs8 rdfs8_derives rdfs_rule_class_subclass_resource ✅ PROVED ✅ PROVED Added 2026-07-31 closing RS-2 (spec comment "NOT IMPLEMENTED" predates this landing — stale at the file's own top-of-file banner, current at the rule definition site). Covers cax-sco in the OWL-RL fixpoint.
rdfs9 rdfs9_derives (rdfs9_derives2 two-source form) rdfs_rule_subClassOf ✅ PROVED ✅ PROVED Two-source split, same shape as rdfs5.
rdfs10 rdfs10_derives none (only the unsound reflexivity approximation) N/A — no sound engine rule ✅ PROVED (spec-predicate level) RS-1, mirrored: C rdfs:subClassOf C for every C typed owl:Class is not RDFS-entailed either. Covers scm-sco naming in adjacent docs.
rdfs11 rdfs11_derives (rdfs11_derives2) rdfs_rule_subClassOf_trans ✅ PROVED ✅ PROVED Two-source split.
rdfs12 rdfs12_derives rdfs_rule_container_membership N/A — engine licenses against the axiomatic table, not this row ✅ PROVED (spec-predicate level) Shipping rule is an unconditional axiom emitter over rdf:_1..rdf:_5 (finite slice — incomplete w.r.t. the infinite family, RS-2, never unsound); its own licensing lemma (rdfs_rule_container_membership_licensed) is stated against rdfs_axiomatic \/ rdfs_member_subproperty, not rdfs12_derives.
rdfs13 rdfs13_derives rdfs_rule_datatype_subclass_literal ✅ PROVED ✅ PROVED Added 2026-07-31 closing RS-2.

Count: 11 of 13 rows PROVED at both licensing and truth (all except rdfs6/rdfs10, whose only shipping approximation is unsound, and rdfs12, whose shipping rule realizes the axiomatic table rather than the premise-driven row). 13 of 13 have truth proved at the spec-predicate level. Composite theorems built on top of the 11: rdfs_licensed_true (any row-licensed triple is true), rdfs_closure_step_sound (one closure step preserves truth), and rdfs_rule_*_preserves per-rule bridges (licensing ∘ truth). The old-2004-syntax variant rdfs1_2004_derives (bnode-substituting form) is UNATTEMPTED/deliberately out of scope — same fresh-term shape as the OWL-RL IMPOSSIBLE rules above.

Naming note (2026-08-08, owner decision): the fragment the rows below call "rho-df" is the literature's ρdf — subPropertyOf / subClassOf / type / domain / range, introduced by Muñoz, Pérez & Gutierrez, "Simple and Efficient Minimal RDFS", J. Web Semantics 7(3), 2009 (author copy: https://users.dcc.uchile.cl/~cgutierr/papers/jws09.pdf), whose minimal deductive system also drops the reflexivity rows exactly as these theorems do. The PUBLIC API name for this fragment is corerdfs (coreRdfsClosure / coreRdfsCheck, with rhoDf* kept as aliases); F* module, function, and theorem names keep the literature transliteration rho_df so this registry greps against both the tree and the paper.

RDFS-Plus tier (2026-08-09, owner-approved): rdfs_plus_closure (RDF.Entailment.RDFSPlus.fst; public API rdfsPlusClosure) composes the shipping rdfs_closure_step with 13 RDFS-Plus OWL rows — eq-sym, eq-trans, eq-rep-s/o/p, prp-symp, prp-trp, prp-inv (both directions), prp-fp, prp-ifp, cax-eqc, prp-eqp — under the same dedup + fuel + length-test loop as rho_df_closure. Tier names: "RDFS-Plus" (Allemang & Hendler, Semantic Web for the Working Ontologist, 2008), "RDFS++" (Franz Inc., AllegroGraph). CLAIM LEVEL: every OWL row runs under its proved licensing + truth lemmas (section 1 of this registry); chain-level completeness is NOT claimed — owl:sameAs equality breaks the Herbrand construction the corerdfs completeness proof uses (that gap is task #10's embedding problem, not an oversight). DEFINITION — "chain-level completeness" (informal name, coined here; not a term of art): the completeness half of the corerdfs tier's composed theorem chain surviving to the query surface — concretely, the conjunction of rho_df_saturation_iff + rdfs_closure_rho_df_complete (Completeness.fst), rho_df_closure_decides (RhoDFClosure.fst), the six-bucket index completeness theorems, and theorem_rdfs_regime_bgp_exact_answer with its ASK corollaries (SPARQL11.EntailmentRegime.RDFS.fst): every entailed consequence is returned by the shipping query entry point, end-to-end, not per-rule. A tier "lacking chain-level completeness" runs only per-rule certificates (licensed + truth-preserving derivations, no exhaustiveness claim against a model theory). eq-ref (sameAs reflexivity) is excluded as a noise row, mirroring rho-df's exclusion of rdfs6/rdfs10.

Experimental entailment regimes (2026-08-09, owner-approved): x-rdfscore and x-rdfsplus are selectable per query (query(data, q, {entail: 'x-rdfscore'}), CLI --entail x-rdfscore), dispatched in F* by RDF.Entailment.RegimeDispatch.fst — consumers pass the string through verbatim. Both are materialisation-based (answers = simple entailment over the named closure), the standard construction for finite answer sets under SPARQL 1.1 Entailment Regimes' extension point; the x- prefix marks them experimental. x-rdfscore is the regime whose definition IS a theorem (theorem_rdfs_regime_bgp_exact_answer + ASK corollaries — see the chain-level completeness definition above); x-rdfsplus carries the RDFS-Plus tier's per-rule certificates. The SPARQL Protocol endpoint does not yet expose an entailment parameter — recorded follow-up, not an accidental gap.

3. Simple / RDF entailment rungs#

Rung Theorem Status Fragment / hypotheses Notes
Simple entailment (RDF 1.1 Semantics § 5) Simple.ModelTheory.interpolation_lemma ✅ PROVED — full iff graph_tt_free (RDF 1.2 triple-term-free fragment) Herbrand construction (herbrand). Stronger than what the adoption proposal asked for ("complete simple entailment characterization").
Simple entailment, composed with shipping search simple_entails_iff_model_theory ✅ PROVED plus graph_exact soundness boundary, SE-1 witness End-to-end: model theory iff the shipping search's answer.
Simple entailment, spec-text instance-subgraph form (unnamed converse) PARKED (choice-based image construction not built) — One direction only; the interpolation lemma above is the stronger result actually used.
Simple entailment, merging lemma Simple.ModelTheory bnode-disjoint union lemmas ✅ PROVED (7a98202) bnode-disjoint g1, g2 Adoption item A5; union satisfaction iff component satisfaction, plus the entailment corollary.
Simple entailment, bnode-restriction graph_bnodes_complete ✅ PROVED (7a98202) — Satisfaction depends only on the assignment's restriction to the graph's own bnode labels (Q3's finite-assignment refinement, without changing the total-assignment convention).
RDF rung, finite axiomatic-table soundness finite_rdf_axioms_sound + is_rdf_axiomatic_triple ✅ PROVED (7a98202) soundness direction ONLY No completeness claim against the infinite rdf:_n family, per the proposal's own rule.
rho-df completeness (RDFS rung) RDFS.Completeness.rho_df_closed_iff / rho_df_saturation_iff / rdfs_closure_rho_df_complete ✅ PROVED (e94e7ad) rho_df_frag_graph (literal-free + tt-free objects, IRI objects on subPropertyOf) + rho_df_closed / is_subgraph / fragment-preservation of the closure result (M1b/M2 discharge) FINDING C-1: the naive statement vs full RDFS entailment is FALSE — machine-checked reflexivity + universality witnesses (rho_df_entailment_strictly_stronger); correct form reduces the interpretation class to the six rho-df conditions. FINDING C-2: shipping twelve-rule closure gets completeness only; the iff needs the six-rule operator (M1b).
Simple entailment, parser-boundary label-independence Boundary.entails_ntriples_boundary ✅ PROVED — —
RDF entailment (§ 8), rdfD2 rdfD2_derives / rdfD2_true / rdf_property_axiom_closure_licensed ✅ PROVED — licensed + true, at rule and closure level — —
RDF entailment (§ 8), rdfD1 rdfD1_derives UNATTEMPTED — deliberately unimplemented mints a fresh blank node (Skolem-family row) Same fresh-term shape as the OWL-RL IMPOSSIBLE rules; excluded from rdf_closed on purpose, not by oversight.
RDF entailment, completeness — N/A — no completeness theorem exists at this rung — Axiomatic tables + rdf:_n schema transcribed; no claim of completeness against the infinite family.
RDFS entailment, rho-df completeness (fragment iff, abstract saturation) RDF.Entailment.RDFS.Completeness.rho_df_saturation_iff ✅ PROVED (G3 M1, landed) — rho_df_entails g e <==> simple_entailment_spec c e for any c extensive/sound/rho-df-closed over g rho_df_frag_graph c, graph_tt_free e Herbrand technique from the Simple rung, reused verbatim. Supersedes the "UNATTEMPTED" row this replaces (#347/#348 index-completeness landed 90e2801). Findings C-1 (the coverage doc's literal gap-1 statement — rdfs_entails d_minimal g e <==> closure-then-simple-entailment — is FALSE; machine-checked witness rho_df_entailment_strictly_stronger) and C-2 (the shipping 12-rule rdfs_closure cannot instantiate c in the SOUNDNESS direction) are recorded in the module banner.
RDFS entailment, rho-df closure operator (G3 M1b) RDF.Entailment.RDFS.RhoDFClosure.rho_df_closure (6 rows: rdfs2/3/5/7/9/11, reusing the shipping rdfs_rule_* functions, fuel/length-test loop) see the 5 theorems below — Answers finding C-2: the six-rule operator rdfs_closure does not expose.
— extensivity rho_df_closure_extensive ✅ PROVED — is_subgraph g (rho_df_closure g fuel) graph_clean g (separator-free labels, no triple-term object — decidable, INPUT graph only) Hypothesis strengthened-away 2026-08-22 (#474, backport of the Lean 4 closure_extensive plan): rho_df_chain_canonical g (no_dup_keys at every fuel-visited graph, uncheckable by any caller) is now DISCHARGED by section 2b, which proves graph_clean preserved by the six-rule step. Satisfiability of the new hypothesis is machine-checked (lemma_graph_clean_satisfiable). rho_df_closure_extensive_chain keeps the old statement for rho_df_closure_decides. FINDING X-1: the Lean theorem carries NO hypothesis and that form is FALSE here — graph_dedup_sort keeps one representative per triple_to_key STRING and that key is not injective on labels containing U+001F, so a two-triple key collision makes rho_df_closure g fuel a proper subset of g. Still composes the six per-row extensivity lemmas RDF.Entailment.RDFS.FixedPoint already proves, reused (not re-derived).
— soundness rho_df_closure_sound ✅ PROVED — rho_df_entails g (rho_df_closure g fuel) rho_df_chain_wf g (ig_wf_sp at every fuel-visited graph) Condition-usage audit (the task's explicit ask): each of the six _true lemmas (ModelTheory.fst) needs EXACTLY one rho_df_conditions conjunct and no other — no finding, the hypothesis-weakening replay the brief predicted.
— closedness rho_df_closure_closed ✅ PROVED (conditional) — at a fuel witness where the length test passes, rho_df_closed (rho_df_closure g fuel) rho_df_frag_graph c, ig_wf_sp (build_indexed c), rho_df_subclass_subjects_iri c, no_dup_keys (rho_df_closure_step_pre_dedup c), no_repeats_p c, no_repeats_p (rho_df_closure_step c), graph_len (rho_df_closure_step c) = graph_len c rdfs7_reaches_fact c DROPPED 2026-08-06 (statement strengthening) — discharged by rdfs_rule_subPropertyOf_reaches, see the RESOLVED row below. One of the seven remaining hypotheses is still a FINDING carried as an explicit fact rather than derived (F-2); the other six are the "same shape as lemma_len_eq_saturated" hypotheses the brief authorized carrying. Of those, no_repeats_p (rho_df_closure_step c) is REDUNDANT since 2026-08-22 (#474): lemma_rho_df_closure_step_no_repeats proves it unconditionally and rho_df_len_eq_saturated no longer asks for it. It still appears in this val and in rho_df_closure_decides because SPARQL11.EntailmentRegime.RDFS.rho_df_decides_hyps transcribes that hypothesis list verbatim and machine-checks the transcription; deleting the clause is a two-file landing left on #474.
— fragment preservation (no unconditional lemma — see finding F-1) ❌ REFUTED as a flat implication — rho_df_frag_graph g ==> rho_df_frag_graph (rho_df_closure g fuel) is FALSE. Machine-checked witness rho_df_frag_preservation_fails: g = [P rdfs:subPropertyOf rdfs:subPropertyOf; a P _:b1] satisfies the fragment predicate; one rdfs7 step derives a rdfs:subPropertyOf _:b1, violating F2 (subPropertyOf-object-must-be-IRI). Carried as an explicit hypothesis into the payoff theorem instead.
— the payoff rho_df_closure_decides ✅ PROVED (conditional) — rho_df_entails g e <==> simple_entailment_spec (rho_df_closure g fuel) e rho_df_chain_canonical g, rho_df_chain_wf g, rho_df_frag_graph c, ig_wf_sp (build_indexed c), rho_df_subclass_subjects_iri c, no_dup_keys (rho_df_closure_step_pre_dedup c), no_repeats_p c, no_repeats_p (rho_df_closure_step c), graph_len (rho_df_closure_step c) = graph_len c, graph_tt_free e (c := rho_df_closure g fuel) rdfs7_reaches_fact c DROPPED 2026-08-06, same reason as closedness above (feeds rho_df_closure_closed). Instantiates rho_df_saturation_iff with the four theorems above.
— FINDING F-2 (rdfs9/subClassOf completeness gap) rdfs_rule_subClassOf_reaches_iri (IRI-class case only) 🟡 PARTIAL — proved for S_IRI-typed classes only rho_df_subclass_subjects_iri c closes the gap for rho_df_closure_closed/_decides The shipping rdfs_rule_subClassOf only chases T_IRI-typed classes (match t.o with T_IRI class_iri -> ... | _ -> acc); rdfs9_derives's declarative xs : subject is unrestricted (also accepts S_BNode). Unlike RS-3 (rdfs3), the two are NOT in refinement here — a genuine completeness gap, not a proof-engineering one. Still open — untouched by the rdfs7 landing below.
— RESOLVED 2026-08-06 (rdfs7/subPropertyOf reaches) rdfs_rule_subPropertyOf_reaches / rdfs_rule_subPropertyOf_reaches2 ✅ PROVED, unconditional — rho_df_frag_graph g /\ ig_wf_sp (build_indexed g) /\ rdfs7_derives g t ==> memP t (rdfs_rule_subPropertyOf g (build_indexed g)) — Was PARKED after 3 proof-side attempts (rdfs7_reaches_fact, a carried hypothesis, not a proved lemma — five attempts total counting the two recorded here). Fix: the engine-side lambda-lift the proof side never tried — RDFS.Closure.fsti now names rdfs7_emit (ig) (q) (acc2) (tt) = emit_once_term ig acc2 tt.s q tt.o and rdfs_rule_subPropertyOf's inner fold calls it directly (behavior-identical), giving the engine step and this proof the SAME first-order symbol instead of two independently-elaborated closures (the closure-identity law, skills/proof-factory/SKILL.md). First-attempt pass once the engine carried the named symbol. Ripple: rdfs_rule_subPropertyOf_licensed (RDF.Entailment.RDFS.Refinement.fst) and lemma_rdfs_rule_subPropertyOf_extensive (RDF.Entailment.RDFS.FixedPoint.fst) re-spelled their local outer_step/inner_step to the same named helper (mechanical, both re-verify). rho_df_closure_closed/_decides and the F-1 witness call site (lemma_f1_bad_triple_derived/rho_df_frag_preservation_fails) all had the rdfs7_reaches_fact hypothesis DROPPED, not just satisfied — see the two rows above.
RDFS entailment, unrestricted completeness — ❌ REFUTED (not merely unproven) axiomatic tables unseeded Stated explicitly in the coverage survey: this is FALSE, not an open question.

4. Index / infrastructure lemmas#

These are not W3C rows; they are the machinery the two families above lean on to make ig_wf_* hypotheses real rather than assumed.

Lemma family Module Status Notes
Index well-formedness (5 buckets) RDF.Indexed.KeyInjectivity.fst ✅ PROVED — ig_wf_sp, ig_wf_subj, ig_wf_obj, ig_wf_po, ig_wf_pred (#338, closed 2026-08-04) sp_key/po_key/subject-key injectivity, one-sided, on U+001F-free keys.
Bucket lookup completeness (converse direction) RDF.Indexed.Completeness.fst ✅ PROVED (90e2801) Generic lemma_build_bucket_complete for any key_of + pred-bucket instantiation, no side condition; first consumer of the #347 StringOrder axioms. Its generic sortWith all-pairs-sortedness machinery also unblocked Gap B below.
Bucket lookup completeness — the other FIVE buckets RDF.Indexed.Completeness.fst stage 6 (2026-08-06) ✅ PROVED lemma_build_indexed_complete_subj / _sp (total keys, no side condition) and _obj / _po / _so (option keys — conditional on bucket_key_X t == Some k, because build_bucket files a literal-object or triple-term-object triple in NO binding). Each is the generic stage-5 lemma at one more key_of. Removes the first half of finding BR-4.
Dedup-key injectivity (literal arm, #348 fix) RDF.Indexed.KeyInjectivity.fst ✅ PROVED (e137b5d) term_to_key_total/triple_to_key full injectivity across all four term shapes under separator-free side conditions; lemma_graph_full_sep_free_no_dup_keys. Engine fix: literal keys now unit_sep-joined (extraction re-gate pending).
Closure-step no-repeats (Gap B) RDF.Entailment.RDFS.FixedPoint.fst ✅ PROVED (e137b5d) — UNCONDITIONAL lemma_rdfs_closure_step_no_repeats; the noeq-vs-sortWith_sorted blocker fell to the Completeness module's eqtype-free sortedness proof. lemma_len_eq_saturated_gapB left only Gap A — CLOSED same day: lemma_len_eq_saturated_sep_free (Gap A landing) makes the termination test faithful for separator-free, tt-object-free, repeat-free inputs. NO open gaps on the termination path.
Row-level separator-freedom RDF.Entailment.RDFS.SepFree.fst ✅ PROVED — per-row conclusion cleanliness for rdfD2 and rdfs1-13 (checked directly: lemma_rdfsN_sep_free per row) Feeds ChainWf.
Closure well-formedness chain RDF.Entailment.RDFS.ChainWf.fst ✅ PROVED — graph_sep_free g ==> closure_chain_wf g, empty + non-empty instances machine-checked Makes rdfs_closure_entails apply to concrete graphs (hypothesis discharged non-vacuously).
Closure step extensivity + fixed-point RDF.Entailment.RDFS.FixedPoint.fst 🟡 LANDED WITH ADJUDICATION (commit 1aa4e71) Length-test fixed-point theorem holds under two explicit hypotheses (no_dup_keys on the pre-dedup intermediate graph), not unconditionally — the unconditional form is FALSE. Third finding from this landing: term_to_key_total literal keys use plain "^^" not unit_sep, a wider dedup-collision surface than #338 described (#348).
String ordering axioms RDF.Indexed.StringOrder.fsti ⚠️ 3 TRUSTED AXIOMS (#347) See Trust surface.

5. SPARQL algebra refinement (the query rung, G3 M3)#

The layer that connects closure results to query answers. Layer 2 landed 2026-08-06 (SPARQL11.Algebra.BGPRefinement.fst); layer 3 — the composed regime theorem — landed 2026-08-06 (SPARQL11.EntailmentRegime.RDFS.fst). Both halves are now proved. SOUNDNESS reached the SHIPPING selective-index store the same day (finding RT-2 resolved: layer 2 parts 2b/8, layer 3 part 8). COMPLETENESS landed 2026-08-06 as well, discharging finding BR-4 (layer 2 parts 2c/9/10/11/12) and removing BOTH named gaps from layer 3 — eval_bgp_complete_at and eval_bgp_store_complete_at — in part 9. Because layer 2's completeness is proved at an ARBITRARY store (the same generalisation RT-2's soundness needed), the unconditional theorems land at the eval_bgp proxy AND at the literal eval_ask_query entry point. The regime iff is unconditional at the ANSWER level; see findings BR-5 / RT-5 for why "answer level" and not "list membership", and RT-6 for the one hypothesis that had to be ADDED.

Layer 2 — BGP refinement against a fixed graph#

Theorem Status Fragment / hypotheses Notes
theorem_eval_bgp_subgraph (shipping statement: every eval_bgp solution subset-matches) ✅ PROVED bgp_frag (tt-free, exact literals, not fulltext), graph_frag Soundness direction of layer 2.
theorem_eval_bgp_instantiates_into_graph (mu(BGP) ⊆ g form) ✅ PROVED same The form layer 3 consumes at rho_df_closure g fuel.
lemma_ig_search_sound + 8 supporting lemmas (single-tp soundness, planner cover, fuel unfolds, extension-closed tp-match) ✅ PROVED per-lemma Fills the gap SPARQL11.Algebra.Refinement's banner recorded (index-probe soundness).
lemma_ig_search_complete_selective (+ the six gated bucket lemmas) — the index probe serves EVERY matching graph triple, at ANY bucket_needs ✅ PROVED (2026-08-06, part 2c) bound_holds b t, bound_obj_exact b (the bound object's rdf_term_eq class is a singleton) Discharges finding BR-4. Mirrors lemma_ig_search_sound_selective branch for branch over the six pick_smaller_bucket candidates. bound_obj_exact is exactly the fragment where BR-4's predicted FALSITY ("x"@en vs "x"@EN) cannot occur — not a weakening, the true side condition. Arbitrary-needs for free, by the same flag split part 2b used for soundness: an unbuilt bucket is never OFFERED, and bucket_cand_complete t None is vacuously true. lemma_ig_search_complete / lemma_store_search_complete / lemma_store_search_complete_for are its three specialisations.
theorem_eval_bgp_store_complete_fuel / theorem_eval_bgp_store_complete — every explained BGP yields a returned solution muo with binding_extends muf muo ✅ PROVED (2026-08-06, part 11) bgp_frag b, graph_frag gs.gs_graph, store_search_complete gs, bgp_subgraph_clause b gs.gs_graph muf The induction over the fan-out fold, counterpart of theorem_eval_bgp_store_sound_fuel, same fuel side condition, same named continuation bgp_fanout_cont, same store-generic hypothesis style.
theorem_eval_bgp_store_complete_answer / _from_subset, theorem_eval_bgp_complete_from_subset, theorem_eval_bgp_store_for_complete_from_subset — the returned solution instantiates the BGP to the SAME triples ✅ PROVED (2026-08-06, part 12) above plus store_search_sound gs, and (for _from_subset) Some? (instantiate_tp p muf) per pattern The form layer 3 consumes, at the full-index store AND at the shipping graph_to_store_for store. Proof composes BOTH halves of layer 2: completeness supplies muo, then SOUNDNESS at muo supplies "every pattern instantiates under muo", which turns binding_extends into answer equality.
memP muf (eval_bgp b g) for a caller-supplied muf ❌ FALSE — finding BR-5 — sm_bind conses, so the evaluator emits one binding ORDER fixed by choose_best_tp's cost estimates; a same-bindings/different-order list is not memP. Two patterns and two variables exhibit it. Not a missing lemma — a wrong statement, corrected to the answer-equality form above.
Domain clause dom(mu) = var(BGP) UNATTEMPTED — Bookkeeping, no graph content; separate landing. It is the ONLY thing between the answer-equality form and a literal smap_eq (hence a literal set equality).

Findings BR-1..BR-3 (module banner, machine-relevant divergences): the fulltext triple-pattern path is NOT subset-matching (binds subject only, applies a limit — excluded by hypothesis, not hidden); query blank nodes match as CONSTANTS (identity renaming — exact for bnode-free BGPs, the regime suite's class); statements are set-membership, not multiset (§18.3.1 cardinality needs a no-duplicates hypothesis, deferred to layer 3's inputs).

Layer 3 — the composed regime theorem (SPARQL11.EntailmentRegime.RDFS.fst)#

Throughout, c := rho_df_closure g fuel. Hypothesis provenance is labelled L2 (layer 2), D (rho_df_closure_decides), or NEW (introduced by this module).

Theorem Status Hypotheses, with provenance Notes
theorem_rdfs_regime_bgp_sound — memP mu (eval_bgp q c) ==> rho_df_entails g (instantiate_bgp q mu) ✅ PROVED L2 BR.bgp_frag q, BR.graph_frag c; D the nine clauses of rho_df_decides_hyps (rho_df_chain_canonical g, rho_df_chain_wf g, rho_df_frag_graph c, ig_wf_sp (build_indexed c), rho_df_subclass_subjects_iri c, no_dup_keys (rho_df_closure_step_pre_dedup c), no_repeats_p c, no_repeats_p (rho_df_closure_step c), graph_len (rho_df_closure_step c) = graph_len c) — transcribed verbatim, sufficiency machine-checked by lemma_decides_hyps_suffices; NEW none The composed regime theorem's soundness half. Needs NO groundness condition (finding RT-1) and NO graph_tt_free (finding RT-3 — discharged from graph_frag c plus the layer-2 subset).
theorem_rdfs_regime_bgp_complete — rho_df_entails g (instantiate_bgp q mu) ==> exists muo. memP muo (eval_bgp q c) /\ instantiate_bgp q muo == instantiate_bgp q mu ✅ PROVED — UNCONDITIONAL (2026-08-06) L2 BR.bgp_frag q, BR.graph_frag c; D rho_df_decides_hyps g fuel; NEW graph_ground (instantiate_bgp q mu) (the scoping decision); NEW bgp_instantiable q mu (finding RT-6) The converse, with the BR-4 gap CLOSED. eval_bgp_complete_at is gone from the hypothesis list. bgp_instantiable replaces nothing — it repairs finding RT-6: instantiate_bgp silently drops a pattern it cannot instantiate, so is_subgraph (instantiate_bgp q mu) c alone is satisfied by a mu that binds nothing.
theorem_rdfs_regime_bgp_exact_answer — the iff ✅ PROVED — UNCONDITIONAL (2026-08-06) union of the two rows above M3's target shape, delivered: the evaluator's ANSWER set over the rho-df closure IS the RDFS regime's answer set, on the fragment, for ground answers. Answer-level rather than list-level per finding RT-5 / BR-5.
theorem_rdfs_regime_ask_complete ✅ PROVED — UNCONDITIONAL (2026-08-06) same as the row above ASK asks only for non-emptiness, so the RT-5 representation gap costs it nothing.
theorem_rdfs_regime_bgp_complete_selective — the same conclusion at the SELECTIVE store graph_to_store_for builds ✅ PROVED — UNCONDITIONAL (2026-08-06) same as the row above The completeness counterpart of theorem_rdfs_regime_bgp_sound_selective. eval_bgp_store_complete_at is gone from the hypothesis list: layer 2 part 2c proves the probe complete at an arbitrary bucket_needs, exactly as part 2b did for soundness.
theorem_rdfs_regime_ask_query_complete — eval_ask_query q c ds == true, at the LITERAL shipping entry point ✅ PROVED — UNCONDITIONAL (2026-08-06) same, plus the bare-BGP ASK shape (q_form == QF_Ask, q_pattern == GP_BGP bgp_q, q_dataset == [], q_values == None) Findings RT-2 and BR-4 both closed at one statement: the store is the selective one the shipping path actually builds, and the completeness fact about it is proved rather than assumed. Mirrors theorem_rdfs_regime_ask_query_sound.
theorem_rdfs_regime_bgp_complete_conditional 🟡 SUPERSEDED (still true) D rho_df_decides_hyps g fuel; NEW graph_ground (instantiate_bgp q mu); NEW eval_bgp_complete_at q c mu Retained. eval_bgp_complete_at cannot be discharged as written (finding RT-5 / BR-5: it asks for list membership of a caller-supplied mapping) but is usable by a caller holding the evaluator's own list.
theorem_rdfs_regime_bgp_exact — the conditional iff 🟡 SUPERSEDED (still true) union of the two rows above Retained alongside theorem_rdfs_regime_bgp_exact_answer.
theorem_rdfs_regime_bgp_complete_conditional_selective, theorem_rdfs_regime_ask_query_complete_conditional 🟡 SUPERSEDED (still true) as above with eval_bgp_store_complete_at q c mu RT-2's shipping-path conditional pair. Retained alongside the unconditional theorem_rdfs_regime_bgp_complete_selective / theorem_rdfs_regime_ask_query_complete.
theorem_rdfs_regime_ask_sound — ask_bgp q c = true ==> exists mu. rho_df_entails g (instantiate_bgp q mu) ✅ PROVED same as the soundness row, plus ask_bgp q c == true ASK corollary. The algebra above BGP is entailment-agnostic (Entailment Regimes §2), so this is non-emptiness of the same solution sequence — inherited, not reproved.
theorem_rdfs_regime_ask_complete_conditional 🟡 SUPERSEDED (still true) same as the conditional completeness row Mirror image; superseded by theorem_rdfs_regime_ask_complete.
GROUND-COLLAPSE BRIDGE, half one: lemma_subgraph_implies_spec — is_subgraph e c ==> simple_entailment_spec c e ✅ PROVED — UNCONDITIONAL none (holds for every e, RDF 1.2 triple terms included) Via the identity substitution, named (id_subst) rather than a lambda — the closure-identity law. This is the half soundness consumes.
GROUND-COLLAPSE BRIDGE, half two: lemma_spec_ground_implies_subgraph — simple_entailment_spec c e ==> is_subgraph e c ✅ PROVED NEW graph_ground e A ground triple's only instance is itself (lemma_triple_inst_ground). RDF 1.1 Semantics §4/§5.3: the interpolation lemma degenerates when e has no blank node.
lemma_ground_entailment_collapse — the iff at ground e ✅ PROVED NEW graph_ground e The bridge as one statement.
lemma_instantiate_bgp_ground — decidable discharge of graph_ground ✅ PROVED NEW bgp_ground_positions q /\ smap_ground mu Both conjuncts are syntactic checks on the query and on the candidate solution — the C1-style route a caller uses instead of inspecting the instantiated graph.
lemma_decides_hyps_suffices ✅ PROVED — The machine check that the transcribed hypothesis bundle is SUFFICIENT for rho_df_closure_decides (catches weakening; verbatim transcription is what a reader checks for strengthening).
lemma_eval_pattern_bgp_is_selective_store ✅ PROVED — The machine-checked record of finding RT-2: eval_pattern on a bare BGP uses graph_to_store_for (selective index), not eval_bgp's graph_to_store.

Findings RT-1..RT-4 (layer-3 module banner):

Layer-3 scoping decisions (settled in the module banner, part 1): fragment-scoped BGPs only; groundness restricts the COMPLETENESS direction only; RDF 1.2 triple terms are classified as non-ground rather than waved through, so RT-3's subsumption is real rather than definitional.

SPARQL11.Algebra.Spec / SPARQL11.Algebra.Refinement (landed 2026-08-03, pre-registry) are now in the verify-rdf-mt roster with this layer; their per-lemma rows are a registry backfill task.

fexpr_congr (g4-fexpr-congr, 2026-08-10). theorem_filter_card's fexpr_congr hypothesis (18.5's bag-level Filter statement needs the expression evaluator to respect smap_eq) is now DISCHARGED by lemma_eval_expr_congr — structural induction over eval_expr_with_base's whole ~74-constructor expr language (plus sibling lemmas for its four and-clique mates and one new helper for option expr fields), ~230 lines. Every mu-read funnels through Lh.assoc_tr (sm_lookup/fx_ctx_get), the same primitive S.sval/S.smap_eq uses, so agreement at every key transfers uniformly. FINDING FC-1: this does NOT reach the literal shipping eval_expr_ebv — it and eval_expr_fwd are irreducible (SPARQL11.Algebra.fst ~4487/4491), and the definitional equation needed to cross back out to the wrapper (eval_expr_ebv base e mu == ebv (eval_expr_with_base base e mu)) is unreachable by every F* technique tried (assert_norm, norm [delta_only [...]] in both string and quoted-name form, blanket delta, nbe, unfold_def; same- module and cross-module) — not a semantic falsity, a proof-engineering wall the qualifier erects on purpose to keep the ~600-line evaluator body out of unrelated callers' SMT context. theorem_filter_card_eval_ expr_with_base restates the card-spec UNCONDITIONALLY at eval_expr_ebv_transparent (the wrapper's own body, copied verbatim without irreducible); theorem_filter_card on the literal eval_expr_ebv / shipping filter_solutions stays hypothesis-carrying pending a dedicated commit auditing removal of irreducible across its ~13+ call sites (SPARQL11.Algebra.Refinement.fst, SPARQL11.Expression. Refinement.fst, SPARQL11.Store.fst, SPARQL.Service.Wrap.fst).

theorem_project_card (g4-project-card, 2026-08-10). Project's bag- layer clause (project_card_spec, Spec.fst:780 — length preserved plus occurs-iff-in_project_spec) is now PROVED UNCONDITIONALLY of the shipping project_solutions (SPARQL11.Algebra.fst:5781, project :5761). No fragment hypothesis, matching theorem_union_card's shape. Unlike SR-1/SR-2/SR-3, project never compares two solution mappings against EACH OTHER with rdf_term_eq — it is a per-row, per-variable lookup — so the rdf_term_eq/term_id_eqb gap those three findings turn on cannot arise here; project_card_spec itself is also already stated at the right level (occurs/length, not raw list equality), so it does not fall into the RT-5 assoc-list-order trap either. Built on the existing theorem_project_is_proj, theorem_project_solutions_ length, theorem_project_solutions_spec assets plus two new congruence lemmas on is_proj's target argument (lemma_is_proj_unique, lemma_is_proj_congr_target). theorem_distinct_card on a smap_eqb-uniform (case-normalized lang tag) fragment was attempted and judged NOT cheap: unlike Project, distinct_solutions's dedup (sm_equal, rdf_term_eq-based) would need its whole mutual-submap structural correctness re-derived under term_id_eqb to connect to mult — a second SR-1-scale proof, not a ~1-attempt lemma — so it was skipped per the task's stop rule rather than forced.

Trust surface#

What this registry's PROVED cells rest on, beyond F*/Z3 itself:

Calibrated claims (adoption item A4)#

State this program as: "proved sound with respect to an independent F* formalization of the W3C RDF/RDFS/OWL semantics, under the stated fragment restrictions and the trust surface above" — never as "a complete formally verified implementation of RDF semantics." The qualifier in CLAUDE.md iron rule #11 (parser and algebra spec verified in F*; on-disk backend has unverified OCaml-side optimization layers) applies here unchanged.

6. G4: response-path theorems (2026-08-09, wave 1)#

Stage Theorem Status Notes
ORDER BY theorem_sort_solutions_permutation ✅ PROVED Multiset-level, hand-derived (stdlib sortWith_permutation needs eqtype; noeq rdf_term fails it — reusable finding). Sortedness NOT claimed: comparator ties break antisymmetry; wave 2.
LIMIT/OFFSET theorem_slice_solutions_window / _length ✅ PROVED Contiguous index-shifted window, all None/Some combinations.
DISTINCT theorem_distinct_complete ✅ PROVED Representative-level, needs noRepeats domains hypothesis.
DISTINCT card theorem_sr3_distinct_card_spec_false ❌ SPEC FALSE FINDING SR-3 (#359): dedup case-insensitive lang tags vs count exact — third strike of the SR-1/SR-2 equality gap.
SRJ (tree layer) lemma_json_val_of_{term,row,vars,rows,response,bool}_roundtrip ✅ PROVED Exact equality, IRI+literal fragment (no bnodes/triple terms/dir literals yet), SPARQL.Protocol.RoundTrip.fst.
SRJ (text layer) Parser.FastString.Axioms.fsti (8 facts / 9 vals, DO-NOT-WIDEN, justified line-by-line vs the OCaml realisation) + lemma_byte_at_after_prefix ✅ PROVED, off trust surface (Step 4, 2026-08-10 — see "FastString migration Step 4" below; was 🟡 UNBLOCKED-as-axioms as of wave 1/2, now real theorems in Parser.FastString.Axioms.fst) fs_byte_length "ab" == 2 provable; all 8 facts now machine-verified true of the real definitions, not merely assumed. parse_json↔serialise text bridge still separately blocked by FStar.String.sub/concat gaps — see docs/designissues/2026-08-10-string-foundation-decision.md.

G4 wave 2b/2c (2026-08-09): ORDER BY sortedness — lemma_sortWith_sorted_by (sorted-under-preorder for stdlib quicksort; totality must be IF-form, OR-form refuted by 2-element counterexample, in-file) + theorem_sort_solutions_sorted (hypotheses carried; IRI fragment discharges via StringOrder axioms; numeric fragment REFUTED — FINDING SR-4 #362, unparseable literal ties everything, transitivity false). FILTER de-vacuation steps 1-2: eval_expr_ebv/eval_expr_fwd assume vals RETIRED — now real [@@ irreducible] definitions over eval_expr_with_base (Part 8 block relocated; the "mutually recursive" banner was stale — Plan agent identifier sweep); glue patch 62 narrowed to 3 symbols. theorem_filter_sound/_complete now state semantics of the real evaluator (existential-free fragment; production-path bridging lemma = plan step 3, next commit).

G4 wave 3 (2026-08-09): M2 expression semantics begins — SPARQL11.Expression.Refinement.fst (proof-only module): ebv_spec (independent §17.2.2 transcription) + error-tolerant §17.3 truth tables + numeric/plain-string equality specs; 17 agreement lemmas on the conforming classes; FINDINGS EX-1 (langString EBV truthy vs spec Type Error) and EX-2 (And/Or/Not can never error — silent collapse), both stated as divergence lemmas with witnesses, #365. Text-bridge: BLOCKED pending owner decision on FastString candidate axioms 7-8 (value-level facts, documented in-file with OCaml justification); tree-level remains the proved boundary meanwhile.

G4 wave 4 (2026-08-09/10): EXISTS-cycle RESOLVED — eval_exists_fwd assume val RETIRED via real mutual recursion (pattern_size/expr_size metric + lemma_substitute_pattern_preserves_size, phase+size decreases per the MathML.Present precedent; z3 cost 1.4-1.6x baseline, under the 3x gate; glue patch 62 down to 2 symbols). Full W3C on the branch: SPARQL 631 pass, 0 fail (out of 631); RDF 1031 pass, 0 fail (out of 1031) — the 4 RIF failures of #367 do NOT reproduce (stale-binary measurement artifact suspected; see issue). RIF enters the theorems zone — RIF.Core.Refinement.fst: rif_fixpoint_extensive/ rif_one_round_extensive/rif_fire_rule_extensive (UNCONDITIONAL — finding E-1: no dedup-pass hypothesis needed, unlike rho-df), rif_derives declarative spec + fire_rule_licensed/ one_round_licensed/fixpoint_licensed (two-graph snapshot idiom forced by intra-round order-dependence). FINDING F-1 (#367 candidate cause): RIF import materialisation hard-codes OWL-Direct closure mode for BOTH regime profiles (RIF.Core.Tests ~150-160 vs OWL.Closure.fsti 5747 mode split) — plausible, unconfirmed.

G4 wave 5a (2026-08-10): N-Triples enters — RDF.NTriples.RoundTrip.fst (proof-only): serializer-side INJECTIVITY family on the IRI/bnode fragment (lemma_nq_term_to_string_* same-shape/cross-shape/injective + pointwise graph level), on real FStar.String primitives. ROUND-TRIP BLOCKED — two findings: the N-Triples PARSER is wholly FastString-based (no tree stage to salvage; even the serializer's escape scan is FastString-gated, blocking ALL literals), so text-level proofs need value-content axioms beyond the approved 8 (extends #358) OR a specified reimplementation of the fast path; and triple-line injectivity is FALSE without a content-safety restriction (IRI containing quote-gt-space forges line boundaries) — the iri_print_safe predicate family is the required guard, aligning with the M1 parser plan's same move.

G4 wave 5b (2026-08-10): FastString axioms 7-8 PROMOTED (owner-approved, #358) — set now 8, DO-NOT-WIDEN. New proved lexing lemmas: build_string length/byte facts (induction over facts 1-4+7) and lemma_quoted_content_byte_sub — the quoted-string read-back step of the SRJ text bridge. FULL text bridge still blocked by a THIRD string-spec wall: FStar.String.concat has ZERO lemmas in ulib (checked; both parser and serializer sit on it). Candidate rule documented in-file, owner-gated. Three walls, one pattern — feeds the re-found-the-fast-path decision on #358.

FastString migration steps 0-1 (2026-08-10): baselines frozen (docs/designissues/2026-08-10-faststring-baselines.md — parse 1M: NT 71,304 tps, Turtle 80,143 tps, RDF/XML 27,669 tps; same-host discipline; bench-turtle-metrics.sh found broken, fallback used, repair noted). Parser.FastString.Spec.fst VERIFIED first-attempt: UTF-8 codec mirroring fs_cp_at_impl branch-for-branch + full lemma kit incl. utf8_decode_encode_identity. Steps 2+3 (the swap + Option-B realisation + equivalence harness, merged together) next.

7. G4/M1: parser round-trip theorems#

Stage Artifact Status Notes
Tokenizer, single-token SPARQL11.Parser.TokenRoundTrip.fst: tokenize_single_fragment_token + 25 per-token next_token_*_pre lemmas ✅ PROVED (9387b830e6) Fragment: all single/two-char delimiter+operator tokens + bare ?. Keywords/IRIs/vars/literals not yet. Canonical-token claim (case-folding bars text-level identity, banner-documented).
Tokenizer, multi-token list tokenize_fragment_roundtrip (+ combine_step, tokenize_loop_step_bridge, tokenize_loop_fragment, six congruence-isolation lemmas) ✅ PROVED (6c3b6f48d1) Literal-reveal fix transferred as predicted; second obstruction (Z3 context blowup on inline concat asserts) solved by isolating steps into near-empty lemmas — pattern banner-documented for the keyword/VAR/IRI widening.
ASK+BGP print round-trip commit 2 (AskBgpRoundTrip) queued Spec in task tracker; fuel-cost lemma per RDF.Indexed.Completeness style.

G4/M1 commit 2 (2026-08-10): SPARQL11.Parser.AskBgpRoundTrip.fst — the ASK+flat-BGP parser chain proved correct at the TOKEN level: parse_select_query_token_level(_query) (given print_query_1's token list, the real parser reconstructs exactly q), via ~35 lemmas through verb/object/subject/triples-block/ggp/ask-body dispatch, with the fuel-cost formula ask_bgp_fuel_cost(n) = n + 11 derived from the real call chain (n=5 → 16 ≪ the 10000 entry fuel; lemmas hold for ANY n). FINDING (fourth string wall, corrects TokenRoundTrip's earlier diagnosis): the TEXT-level theorem is PROVED IMPOSSIBLE with current ulib — FStar.String.sub's type gives the result LENGTH but says NOTHING about its characters; blocks every payload-carrying token (IRIs, vars, even keywords). Fix requires either a trusted ulib-level sub-content rule or lexer changes off substring — owner-gated, consolidated with the #358 string-foundation decision.

FastString migration steps 2+3 MERGED (2026-08-10): six primitives are REAL Spec-backed F* definitions (assume vals retired; sole survivor unsafe_char_of_d7ff in CharBoundary); patch 89 → 1 symbol; fast OCaml now an experimental_ocaml_glue SPEED patch (rule 11(b)). Gates: 233/234 verify (1 = pre-existing #327); extract/compile clean, 29 binaries; W3C SPARQL 631 pass 0 fail (of 631) + RDF 1031 pass 0 fail (of 1031), exact match (OWL DL suite not run, stated); benchmarks ALL within the 10% gate vs frozen baselines (1M parse rows FASTER: nt -8.0%, turtle -3.1%); equivalence 401/401 on valid UTF-8. WALL (documented in the plan doc's Step 2/3 results): Spec's utf8_bytes mixes two decoders that disagree on INVALID UTF-8 (665 divergences + one crash path) — the deletability promise holds for valid input only until the Spec is re-founded on a single WHATWG decoder; repair scheduled before step 4's axiom discharge.

FastString #374 repair (2026-08-10): Spec re-founded — the "second decoder" was ulib's own list_of_string, which returns NEGATIVE codepoints on invalid UTF-8 (measured -1670; a genuine F* stdlib bug, upstream-reportable). Guards added in utf8_enc_char + utf8_decode_all_aux (verified first-try; two new lemmas). Equivalence corpus now COMPLETES CLEAN: 93,846 pass, 0 unexpected fail, 962 documented expected-fail rows (the necessity-forced byte_sub boundary domain), no crash. Deletability contract now holds on ALL input. W3C regression: RDF 1031/0 exact; SPARQL 627/4 with the 4 = the known intermittent RIF quartet (#367's environmental pattern). Residual: decode-of-encode-vs-list_of_string theorem removed after 3 attempts (in-file note with exact error); run-all.sh module-list gap found (separate issue).

FastString migration Step 4 (2026-08-10, branch faststring-step4b): Parser.FastString.Axioms.fst lands — companion .fst proving ALL 8 facts (9 vals) Parser.FastString.Axioms.fsti states, from the real Parser.FastString.Spec definitions via Parser.FastString.fsti's bridging lemmas. The axiom module is now off the trust surface (theorems, not axioms). Two findings: (1) fact 6 (fs_cp_at_ascii) was FALSE as originally stated — a machine-checked counterexample (s = "", pos = 5) shows fs_byte_at's and fs_cp_at's independent out-of-range sentinels (0 vs 0xFFFD) disagree, so the unbounded hypothesis did not imply agreement; fixed by adding the missing pos < fs_byte_length s side condition (zero consumer churn, zero current callers). (2) Fact 8 (fs_byte_sub_self) needed exactly the "single-decoder round trip" theorem (utf8_decode_all_utf8_bytes_identity) this file's own #374-repair entry above records as "removed after 3 attempts" — proved this session via an explicit non-recursive unfold lemma for utf8_decode_all_aux plus a prefix-shift induction and a char-list induction; full account in Parser.FastString.Axioms.fst. Verified: Parser.FastString.Axioms.fst, Parser.FastString.RoundTripLemmas .fst, SPARQL.Protocol.RoundTrip.fst, RDF.NTriples.RoundTrip.fst (all make <file>.checked, rc=0). No admit, no --lax, no new assume val. Companion owner-decision doc for the remaining ulib string gaps (FStar.String.sub/concat, list_of_string on invalid UTF-8): docs/designissues/2026-08-10-string-foundation-decision.md.

G4 step 7 (2026-08-10): eval_select_query folded into the real recursion — eval_subselect_fwd assume val RETIRED (patch 62 now ONE symbol: eval_property_path_fwd). Metric extended: query_size, GP_SubSelect counts its query, pattern_size >= 1 refinement covers the LATERAL edge free; two preservation lemmas (lemma_lateral_substitute_preserves_size with a closure-identity lambda-lift, and the unforecast lemma_rewrite_query_bnodes_pattern_preserves_size — the bnode rewrite walks into sub-queries, the one real gap found). No fuel fallback needed anywhere. All four dependent proof modules re-verify unchanged. W3C clean: SPARQL 631 pass 0 fail (of 631), RDF 1031 pass 0 fail (of 1031).

FastString migration step 5 MERGED (2026-08-10): two new proof assets. (a) Parser.FastString.BaseCases.fst (proof-only): 12 delimiter-character byte facts (quote/braces/brackets/colon/comma/ angle-brackets/space/newline/backslash — fs_byte_at_* + fs_byte_length_* pairs) proved through the .fsti bridging lemmas + Spec.utf8_bytes_ascii_singleton, deliberately independent of the Axioms module; plus a Spec-direct ASCII-content family (lemma_build_string_utf8_bytes/_byte_length/_byte_at). (b) Parser.FastString.ConcatSpec.fst (wired, extracted): concat_spec with proved equations concat_spec_nil/_singleton/_cons — closes string wall (2) (ulib FStar.String.concat has zero equations); proof-critical call sites migrated (Parser.JSON.fst 1 use, SPARQL.Protocol.fst 6 uses). Gates: verify clean, extract/compile clean, W3C SPARQL 627 pass 4 fail (of 631; the intermittent RIF quartet #367) + RDF 1031 pass 0 fail (of 1031), benchmark every row inside the 10% gate vs frozen baselines (all faster; attributed to a quieter host, not the diff — stated in the plan doc). Unblocks the SRJ text bridge (M4) and N-Triples parser-side proofs.

G4 M4 first SRJ text-level lemmas (2026-08-10, branch srj-text-bridge): SPARQL.Protocol.RoundTrip.fst +99 lines, three lemmas verified (10/10 repeated runs, deterministic): lemma_concat_spec_two (fully symbolic two-element decomposition of concat_spec "" — the shape serialise_response_json's terminal join takes on 2+ result rows), lemma_serialise_response_json_empty_literal (closed literal: serialise_response_json [] [] equals the exact SRJ empty-response string, by assert_norm), and lemma_serialise_response_json_two_empty_rows_literal (two empty rows, exercising concat_spec_cons end-to-end on a concrete list). FINDING recorded in-file: the fully symbolic serializer statement does not verify reliably — Prims.strcat is itself an opaque val with no identity/associativity equations (same wall shape as FStar.String.concat, different primitive; provable via FStar.String.list_of_concat + list append lemmas + string_of_list_of_string), and which statement failed shifted with unrelated earlier declarations (Z3 context sensitivity). Next step: interactive diagnosis via fstar-mcp, not batch retries.

Task #48 streaming N-Quads, phase 1 (2026-08-11, branch streaming-nquads): new RDF.NQuads.Streaming.fst (537 lines, proof-only, verified clean, zero assume vals). Strategy B (common line-splitter) on FStar.String.list_of_string/string_of_list (their ulib round-trip lemmas are unconditional — avoids the parked byte-level single-decoder round-trip, #374). PROVED: the split machinery end-to-end — split_complete_lines_reconstruct (complete ^ carry == s, nothing dropped or duplicated), carry-never-contains-newline, "a chunk with no newline extends carry", "a chunk ending in newline empties carry"; the streaming API (stream_state/feed_chunk/finish/stream_parse) defined on the UNMODIFIED Parser.NQuads.parse_nquads_acc; stream_parse_single_chunk_shape; cong_string_of_list (finding: string_of_list gets no SMT congruence from a variable equal to a literal — helper proved by typing substitution, not Z3). Fuel: every parse call runs a fresh string at position 0 with its own fs_byte_length + 1 fuel, so the batch entry point's sufficiency argument applies verbatim. NOT PROVED (in-file FINDING, exact missing lemmas named): theorem_stream_eq_batch — needs parse_nquads_acc_concat_line via (1) a mechanical fs_byte_index_eq bridging lemma and (2) a locality proof across Parser.NTriples.fst's recursive-descent stack, scoped as its own landing.

G4 M1-adjacent: first N-Triples PARSER round-trip theorem (2026-08-11, branch ntriples-parser-lemmas): three landings. (1) fs_byte_index_eq added to Parser.FastString.fst/.fsti — the one bridging lemma missing after the re-founding: parser byte-dispatch reads through fs_byte_index, not fs_byte_at, and had no spec bridge (probe-confirmed unprovable before). Also prerequisite (1) of theorem_stream_eq_batch (task #48 finding). (2) checkpoint_a_closed_triple_round_trip in RDF.NTriples.RoundTrip.fst: parse_triple on the SERIALIZER'S OWN OUTPUT for a concrete all-IRI triple returns exactly that triple — the statement the module banner's FINDING 1 had recorded as unprovable for any concrete input. Plus lemma_extract_middle, a reusable fs_byte_sub extraction helper (Axioms facts only, deliberately avoiding the string_of_list/list_of_string chain that fails even with every fact in context). (3) FINDING (checkpoint (b) not landed): symbolic term-level round-trip needs a scan_iri_end shift lemma, and one layer below it, "" ^ s == s and ^-associativity FAIL for SYMBOLIC strings via plain SMT (concrete literals fine) — the route forward is position/byte-value idioms (the lemma_build_string_byte_at family), never raw symbolic string equality. All verified clean, no admits, no new assume vals.

Task #48 streaming phase 2 (2026-08-11, branch streaming-phase2): two locality lemmas verified in RDF.NQuads.Streaming.fst: lemma_fs_byte_index_concat (byte i of a ^ b reads from a or b by position — closes the phase-1 FINDING's item 1 via the landed fs_byte_index_eq) and lemma_byte_index_at_middle (LINE-level locality: a line embedded at a position inside a larger string reads byte-for-byte the same as the line alone). FINDING updated: item 2 (parse_nquads_acc_concat_line) deliberately NOT attempted — the same proof shape for ONE combinator (scan_iri_end) is already documented in RDF.NTriples.RoundTrip.fst Part 6 as needing a multi-step induction beyond the 3-attempt guard; the full Parser.NTriples.fst call graph is strictly larger. The remaining work for theorem_stream_eq_batch is one dedicated landing: the parser-locality induction over the recursive-descent stack, now reduced to named lemmas with the byte-level base facts in place.

G4 M4 symbolic strcat kit + single-row SRJ theorem (2026-08-11, branch srj-symbolic): three strcat lemmas landed in Parser.FastString.ConcatSpec.fst (the shared home — SRJ and future N-Triples symbolic work both need them): lemma_strcat_empty_l ("" ^ s == s), lemma_strcat_empty_r (s ^ "" == s), lemma_strcat_assoc — proved via FStar.String.list_of_concat → list @ equations (append_l_nil/append_assoc) → string_of_list_of_string, closing the symbolic-strcat wall both the SRJ and N-Triples FINDINGs named. Then SPARQL.Protocol.RoundTrip.fst Part 10: the SYMBOLIC single-row serializer theorem (serialise_response_json vars [r] for symbolic vars and r) verifies. FINDING: the two-row symbolic statement verifies STANDALONE (3 of 3 probe runs) but fails in-file with a fast unknown (incomplete quantifiers) — trigger sensitivity from earlier declarations, not resources (#restart-solver + 6x rlimit ruled out); exact query-stats + bisection plan recorded in-file. Next: the N-row induction (may be easier than fixed two-row — the induction hypothesis adds structure), with fstar-mcp for the bisection.

Parser locality pilot PROVED (2026-08-11, branch parser-locality): new proof-only Parser.NTriples.Locality.fst. lemma_scan_iri_end_shift — the pilot of the locality induction both theorem_stream_eq_batch (task #48) and the symbolic N-Triples round-trip need: if scan_iri_end finds > inside a line, the same scan on the line embedded in prefix ^ (line ^ suffix) finds it at the shifted position. Proved first-attempt by a rec lemma mirroring the combinator's own recursion branch-for-branch, entirely in byte-index/position terms (never symbolic string equality — the one register that avoids the strcat wall). Plus lemma_byte_index_at_middle (self-contained restatement) and the i=0 corollary lemma_scan_iri_end_shift_from_start (the shape parse_iri_raw calls). Template assessment (file-end banner): parse_iri_raw/parse_iri/pws and the position-only skeleton of parse_subject/parse_object are mechanical repetitions; parse_iri_body_acc + parse_literal escape decoding are NEW difficulty (accumulator finished with FStar.String.concat — the separately-named wall; note the strcat kit in ConcatSpec may now apply). No admits, no --lax, no new assume vals.

Parser locality stages 1+2 (2026-08-11, branch locality-fanout): Parser.NTriples.Locality.fst extended (7 commits, all verified). STAGE 1 (mechanical fan-out of the pilot template): shift lemmas with fuel headroom for scan_iri_end, the whitespace scanner (ptake_while_acc_pos), pws, parse_iri_raw (fast path), parse_iri (success case), parse_subject/parse_object (IRI branches). STAGE 2: lemma_parse_iri_body_acc_shift — the escape-handling accumulator combinator, first-attempt: NO concat algebra needed; both runs build the SAME accumulator list, so the final strings are equal by congruence (the pilot banner's hard-prediction was wrong and has been corrected in-file — documented guess vs measured outcome). One non-required statement failed 3 attempts (combined fast+escape parse_iri_raw lemma) — FINDING in-file with all 3 attempts. STAGE 3 NOT started, per the brief's own gate (stages 1+2 do not yet cover a whole line): remaining, in order — blank-node locality (needs a new fs_cp_at fact, character level), parse_literal (Stage-2 method expected to apply), parse_opt_graph_label/parse_graph_label, comment/blank-line scanners, whole-line parse_nquad shift, parse_nquads_acc_concat_line, theorem_stream_eq_batch single-chunk then general.

Parser locality stage 3, items 1-5 (2026-08-11, branch locality-stage3): Parser.NTriples.Locality.fst grown to ~2100 lines, all verified. Item 1 blank-node: utf8_decode_at_join + lemma_cp_at_at_middle — fs_cp_at agrees under embedding given only that the suffix's first byte is not a UTF-8 continuation byte (weaker and more useful than byte-headroom; automatically true at every real line-streaming call site), composed into lemma_scan_bnode_body_cp_shift_headroom. Item 2 literal: lemma_parse_string_body_shift (equal-accumulator), fastpath/escapepath split lemmas, lang-tag + datatype shifts; FINDING — the top-level parse_literal wrapper has a genuine non-local edge case (mid ending exactly at the literal's end changes branch dispatch under embedding). Item 3 graph labels: IRI branch done. Item 4: lemma_skip_eol_shift done; NEW OBSTACLE CLASS — skip_comment/skip_line wrap their scan loops in local unexported let recs, which no external lemma can name (confirmed against F* name resolution, not inferred from failures); fix is the repo's established source-level lift (task #36 pattern: name inner recursions top-level), an extracted-code change out of scope for a proof-only landing. Item 5: lemma_parse_nquad_iri_nograph_shift — whole-line shift for all-IRI, no-graph-label, success case, first attempt. Items 6-8 (parse_nquads_acc_concat_line, theorem_stream_eq_batch) correctly gated on: the skip_comment/skip_line lift + the parse_bnode/parse_literal wrapper chaining — five NAMED remaining pieces, third independent confirmation this is separate-landing work.

Scanner lift + shift lemmas (2026-08-11, branch scanner-lift): the stage-3 item-4 obstacle removed at the source level (task #36 pattern: name inner recursions top-level). nt_skip_to_eol lifted from skip_comment (Parser.NTriples.fst); FIVE byte-identical local skip_line loops (parse_nquads_acc, fold_nquads_acc, count_nquads_acc, parse_nquads_12_acc, parse_nquads_flat_acc) deduplicated into ONE top-level nq_skip_line (Parser.NQuads.fst). Behavior-identical (same branches, arithmetic, fuel). Gates: verify clean across the five affected proof modules; extract+compile OK; W3C SPARQL 631 pass, 0 fail (out of 631) AND RDF 1031 pass, 0 fail (out of 1031) — fully clean run. Shift lemmas lemma_nt_skip_to_eol_shift + lemma_nq_skip_line_shift landed in Parser.NTriples.Locality.fst with a corrected hypothesis (p + fuel > fs_byte_length mid): these scanners RETURN the current position on fuel exhaustion instead of failing (unlike scan_iri_end), so unconditional fuel headroom is false — the condition matches every real caller. Items 6-8 (parse_nquads_acc_concat_line, theorem_stream_eq_batch) now have the scanner path open; they still need the parse_bnode/parse_literal WRAPPER chainings (items 1-2 residue) first.

Task #48: SINGLE-CHUNK STREAMING THEOREM (2026-08-11, branch stream-theorem): theorem_stream_eq_batch_single_chunk PROVED in RDF.NQuads.Streaming.fst — stream_parse [c] == batch_parse c for any chunk that either contains no newline or ends in a newline, with NO hypothesis on the chunk's RDF content (well-formed or not, any shape, any escapes). Supporting landings in Parser.NTriples.Locality.fst: lemma_parse_bnode_shift (wrapper chain closed first-attempt, + subject/object/graph-label bnode corollaries); lemma_parse_literal_plain_shift + lemma_parse_literal_lang_shift (the ^^dt datatype branch is a FINDING — 3 structurally different attempts, identical Error 19, same wall class as the abandoned parse_iri_raw capstone, third independent confirmation); lemma_parse_nquad_shift_generic — a more scalable design than 18 shape combinations: per-component success supplied as external hypotheses, covering every shape by composition. parse_nquads_acc_concat_line: BOTH boundary cases (complete="" / carry="") closed fully generally — neither needed the per-combinator induction the original FINDING predicted (STATUS UPDATE banner corrects it). Remaining for the general multi-chunk theorem: lemma_parse_nquads_acc_restart (named FINDING) — the mid-line-split restart argument. No admits, no --lax, no new assume vals.

Gap 1 CLOSED — SPARQL lexer on fs_byte_sub (2026-08-11, branch lexer-faststring, task #52, owner decision "1: A"): the lexer's single substring choke point (SPARQL11.Parser.fst:225, 13 call sites) migrated from FStar.String.sub (no content spec in ulib) to Parser.FastString.fs_byte_sub (fully proved). Byte-vs-codepoint: the lexer was codepoint-indexed; byte-walking is behavior-identical for THIS grammar because delimiter scans hunt single ASCII bytes (UTF-8 self-synchronizing) and every non-ASCII classification uses code >= 0x80, satisfied by every byte of a multi-byte sequence exactly as by the codepoint; one genuine mixed-unit site (split_pname length arg) found and fixed. SPARQL11.Parser.TokenRoundTrip.fst fully re-proved (ascii_string hypothesis threaded ~30 sites + a reusable ASCII↔fs_byte_* bridging kit in RoundTripLemmas). Gates: W3C SPARQL 631 pass, 0 fail (of 631); RDF 1031 pass, 0 fail (of 1031); bench within the 10% gate (RUNS=5, largest delta +6.3%; a first RUNS=3 pass showed two rows over gate, not reproduced). PAYOFF: fs_sub_of_concat + fs_sub_of_concat_literal — the two probe lemmas AskBgpRoundTrip.fst documented as IMPOSSIBLE against String.sub — now PROVE first-attempt, plus ask_keyword_recovered_from_prefix (ASK keyword recovered from a printed prefix with symbolic tail). Zero new assume vals, zero new axioms. Full string-to-AST theorem remains scoped future work (fragment widening at TokenRoundTrip scale).

G4 M1: SYMBOLIC IRI round-trip theorem (2026-08-11, branch nt-symbolic2): RDF.NTriples.RoundTrip.fst Part 7 (+191 lines). lemma_term_iri_round_trip_build_string — parse of (nq_term_to_string (T_IRI iri)) == the term, for a SYMBOLIC iri whose text is build_string cs with cs any ASCII codepoint list of IRI-safe characters (no escapes, no '>', no controls). The REAL serializer, not a transcription. Sub-checkpoints: lemma_scan_iri_end_build_string (b1 — the scan finds '>' right after any safe IRI text, any suffix) and lemma_parse_iri_raw_build_string (b2). Proof composes two prior landings read-only: the strcat kit (ConcatSpec) and the scan shift lemmas (Parser.NTriples.Locality) — first-attempt pass once composed; the walls the original checkpoint-(b) attempt recorded are both CLOSED by that prior work. Remaining narrowest gap: covering ANY well-formed IRI string requires "s == build_string (codes of s)", the known string-conversion mixing wall (already probed and blocked once, per RoundTripLemmas' notes). No admits, no --lax, no new assume vals.

FastString migration COMPLETE — steps 0-6 (2026-08-11, branch js-equivalence, task #47): the final step-6 item landed — the FastString equivalence corpus now runs UNDER NODE via js_of_ocaml (same OCaml harness, bytecode → jsoo, product build flags): 93,846 pass, 962 expected-fail, 0 unexpected fail (out of 94,808) — IDENTICAL to native (verified three ways: fresh node run, fresh same-source native compile, prior recorded number). The plan's UTF-16/jsoo risk is closed by measurement. Wired into tests/unit/run-all.sh (--jsoo / WITH_JSOO=1) for CI. CORRECTION found: the plan doc's "bytes-as-JS-chars" domain claim was wrong — jsoo 6.4.1 builds with use-js-string=true (checked in both the new test JS and the product factoidal.js); plan doc corrected. The migration end state: zero assume vals in the FastString family except the documented unsafe_char_of_d7ff; all 8 axiom facts proved (one was false and corrected); fast OCaml is a deletable rule-11(b) speed patch; equivalence proven on all input classes, native AND node; benchmarks inside the gate throughout; the String.sub and String.concat ulib walls closed with zero new axioms.

Task #48 general theorem, attempt round (2026-08-11, branch stream-general2): two new primitives PROVED in RDF.NQuads.Streaming.fst: lemma_skip_comment_shift (links skip_comment to the lifted nt_skip_to_eol shift — the one-line gap the file noted) and lemma_nq_skip_line_shift_exact (skip-line shift at the EXACT fuel the engine passes, no headroom hypothesis needed). General theorem_stream_eq_batch: NOT closed — 3 attempts on the narrowest sub-case (blank-lines-only mid) failed, each after 10-16 min of full z3 search (resource exhaustion, not quick rejection). DIAGNOSIS recorded as the FINDING: the hypothesis was a forall over positions with an inner match — no instantiation pattern for the solver; every PROVEN lemma in these files instead passes per-position facts as EXPLICIT witness parameters (stop_pos, gt_pos, ...). Fix path: restate with explicit witnesses at each step. Failed code removed; only proven code kept.

Task #48: GENERAL single-chunk theorem via witness chains (2026-08-11, branch stream-witness): the explicit-witness restructure WORKED — the diagnosis was right. RDF.NQuads.Streaming.fst +839 lines, 9 commits, every z3 query under 25 SECONDS (the forall shape burned 10-16 minutes per failure). New witness types (line_kind, per-kind witness records, line_witness, recursive-chain predicates chain_wf/chain_end/chain_ds_fold — structural induction, never forall-over-positions). ALL FOUR line kinds proved first-try (blank, comment, quad-failure with stated embedded-failure premise, quad-success composing lemma_parse_nquad_shift_generic). LANDED: lemma_parse_nquads_acc_restart (the FINDING's named target), lemma_parse_nquads_acc_concat_line_general (the "genuinely hard case"), and theorem_stream_eq_batch_single_chunk_general — stream_parse [c] == batch_parse c for ANY chunk, any mix of blank/comment/quad lines, any newline placement, given matching witness chains for the split. REMAINING (FINDING, no wall): the multi-chunk fold — one chain-concatenation lemma + a fold induction carrying a witness-chain list; scoping choice, not a failure.

Task #48 / #402: MULTI-CHUNK STREAMING THEOREM (2026-08-11, branch stream-multichunk): theorem_stream_eq_batch PROVED — stream_parse chunks == batch_parse (concat_all chunks) for ANY list of chunks, given a witness-chain pair per chunk (stream_fold_wf). The terabyte-file question now has its answer as a machine-checked theorem chain: split machinery → per-combinator locality → restart → general concat-line law → single-chunk general → MULTI-CHUNK. Design choice recorded: the fold asks the caller to supply remaining-tail witness chains directly (less proof work, more caller burden) instead of the FINDING's bottom-up chain-append route — and the reusable chain_append lemma was ALSO landed separately (shifted, joined chains cover mid_a ^ mid_b), with per-kind extend/shift sub-lemmas. string_concat_assoc proved (not automatic). One non-required piece open with a FINDING + exact fix path (lw_ds_step_extend_right, 3 attempts past the 3-min z3 cap). Whole module verifies in 43 seconds. No admits, no --lax, no new assume vals.

G4 M4: N-ROW symbolic SRJ theorem (2026-08-11, branch srj-nrow): lemma_srj_n_rows in SPARQL.Protocol.RoundTrip.fst Part 11 — serialise_response_json vars rows equals the fixed SRJ shape (head/vars block ^ comma-joined row texts) for ANY variable list and ANY row list of ANY length. First-attempt via induction; the fixed two-row goal that kept failing in-file is now a definitional instantiation. Helpers: chunks_of, json_rows_joined, lemma_json_rows_body_acc_is_rev_acc (accumulator == rev_acc, list facts only), lemma_body_pieces_eq, lemma_concat_chunks (strcat kit inside the induction step, never one flat goal). Residual finding kept: a separate NAMED two-row corollary lemma still trips the file's trigger sensitivity (3 phrasings, same Error 19; one attempt flipped pass→fail by removing an unrelated nearby declaration) — covered by instantiation, not forced. Verified 3x alone + 3x in-file. No admits, no --lax, no new assume vals.

build_string totality wall — sharpened (2026-08-11, branch build-string-total): s == build_string (codes_of s) NOT closed; the finding is now precise. ROOT CAUSE (new, replaces the old "chained equality" explanation): a SELF-RECURSIVE F* Lemma cannot use FStar.String.list_of_string/string_of_list inside its own recursive call — the failure is AT the recursive call, even when nothing uses its result. Control tests: fuel/rlimit don't help; reproduces with a plain int function; a helper lemma verifies alone but fails inside the recursion; calc blocks and #restart-solver don't help; ~20 probe files under --split_queries always. (Toolchain-shaped finding — stays internal per the owner's no-upstream-filing rule.) Route 2 (fs_byte_sub decomposition) needs a not-yet-proven slice_bytes/utf8_decode_all base fact — a different, smaller wall, noted at fs_byte_sub_eq. Findings recorded in Parser.FastString.RoundTripLemmas.fst ("SHARPENED FINDING" section)

#402: CONSUMER layer theorems (2026-08-11, branch consumer-hom): RDF.NQuads.Streaming.fst +~420 lines. API: stream_consume folds each parsed quad into consumer state in ARRIVAL order; per-chunk state is (consumer-state, stream_state) ONLY — no retained quads/dataset (constant-memory observation documented definitionally). Reuses the existing Parser.NQuads.fold_nquads_acc — no new parsing logic. PROVED: single-chunk generic theorems (theorem_stream_consume_single_chunk{,_no_newline,_ends_in_newline}, generic in consumer type, no witness chains) and theorem_stream_consume_dataset_eq_batch — fully general multi-chunk for the dataset-building consumer, routed through theorem_stream_eq_batch via the new witness-free bridge fold_nquads_acc_eq_parse_nquads_acc (the two folds are branch-identical under never_stop). DESIGN NOTE (argued, not just asserted): grouped-by-graph output order is impossible for ANY bounded-memory consumer when graphs interleave across chunks — arrival order is the correct target. FINDING: the both-fully-generic theorem (any consumer x any chunks) is mechanical over the witness-chain kit but a ~670-line pass — separate landing. Verify clean, no admits, no --lax, no new assume vals.

Spec codec: slice law + parked identity RESOLVED (2026-08-11, branch slice-decode): Parser.FastString.Spec.fst Section 7 — utf8_decode_all_concatMap_identity (single-decoder round trip at the codepoint-list level, NO string conversions in the induction — sidesteps the recursion wall by construction) and its string-level corollary utf8_decode_all_utf8_bytes_identity, which RESOLVES the file's own item-6 "ATTEMPTED, PARKED" banner (banner kept with an UPDATE paragraph, rule #14). THE SLICE LAW (utf8_decode_all_slice_by_charcount): decoding a char-count-aligned byte slice yields exactly the char segment. Stated in char-count form (byte offsets = byte-lengths of encoded prefixes) — the is_cp_boundary converse (arbitrary boundary position == some char-count prefix length) is documented as the next rung, not attempted, not needed for the targets. In RoundTripLemmas: fs_byte_sub_by_charcount DISCHARGES fs_byte_sub_eq's pending off-domain note by direct composition. Every lemma first-attempt (technique pre-confirmed by Axioms.fst's private Fact-8 proof). All six consumer modules re-verified clean. No admits, no --lax, no new assume vals.

G1 REVIEW KERNEL delivered (2026-08-11, branch review-kernel): docs/review-kernel.md — 256 lines, readable in one sitting. 21 kernel statements: parser 4, expressions 2, filters 3, modifiers 4, results 2, streaming 3, entailment chain 3 — each with the verbatim F* statement + file:line, its spec predicates, its stated domain in one sentence, and its honest boundary. Plus §8 five named not-covered gaps, §9 trust-surface table (assume-val realisations, z3, F*, the two documented toolchain findings), §10 the override guarantee with exact re-verify commands (no-lax/no-admit claim re-measured: zero pragma matches outside comments). Every file:line read at source while writing. Four drift findings reported to the hygiene tracker (#198), incl. a stale "admit()'d" banner over a clean () body in the COTTAS backend.

#403 npm batch (2026-08-11, branch npm-batch): closure API exposed on all three npm surfaces (index/wasm/fn): coreRdfsClosure/coreRdfsCheck/rhoDfClosure/rhoDfFragmentCheck/ rdfsPlusClosure (+ owlIsConsistent/owlEntails already present). CLAIMS BLOCK added to npm/factoidal/version.json — 8 items each naming its theorem, file, and registry section (all verified to exist verbatim before writing), including the four new landmarks (multi-chunk streaming, N-row SRJ, symbolic IRI round-trip, completed FastString migration); build-ocaml.sh's npm step now preserves the block across rebuilds (verified byte-identical). Gates: npm units 169 pass, 0 fail, 1 skip (of 170); hub node 266 pass, 0 fail (of 266); headless browser 32 pass, 0 fail (of 32); smoke + dts-drift clean. Bundle-sync drift found+fixed (hazard #12: npm copies 2 days behind docs/fstar-extracted). DEFERRED with reasons: package rename to factoidal (owner-decided; ~20-file migration, own dispatch); #344 (F* parser strictness, not ABI); wasm-entry ABI rebuild (needs full extraction — pre-existing RDFS_Closure_SemiNaive.ml gap noted).

#401 M1: IRI round-trip GENERALIZED to plain strings (2026-08-11, branch iri-general): lemma_term_iri_round_trip (RDF.NTriples.RoundTrip.fst Part 8) — parse_object of nq_term_to_string (T_IRI i) == ParseOk (T_IRI i) for ANY string i satisfying is_iri + all_ascii + iri-body-chars; no caller-built codepoint witness. Enabled by lemma_ascii_string_is_build_string (+ _bc variant for BaseCases' nominally distinct pair) in RoundTripLemmas — the ASCII instance of the build_string wall, CLOSED. Route: never let Spec.utf8_bytes-of-build_string appear as a goal inside recursion (that alone reopens the wall — measured, 109ms Error 19); instead byte facts through the Axioms-opaque lemma_build_string_byte_length/byte_at + fs_byte*_eq bridges, list equality by index_extensionality, one plain list-level recursive helper, composed with PR #409's decode identity + string_of_list_of_string. Also found: BaseCases' lemma_build_string_utf8_bytes verifies in the "failing" shape — candidate factors (cons-built RHS vs concatMap; default fuel vs 4/4) recorded, not fully isolated. NOT covered: non-ASCII IRIs (needs multi-byte scan_iri_end advance — separate rung); a generalized checkpoint_a (arbitrary whole triple) is a new composition chain, not mechanical. Full dependency chain re-verified clean. No admits, no --lax, no new assume vals.

#402 COMPLETE: fully-generic consumer theorem (2026-08-11, branch generic-consumer): theorem_stream_consume_eq_batch — for ANY consumer type, ANY consume function, ANY chunk split (same stream_fold_wf witness premise, same generality as the dataset theorem). Design: generic layer ADDED ALONGSIDE the proven dataset-specific layer, not a retrofit — the witness types and position predicates were already consumer-free and reused unchanged; only the per-step transform is parametrized (lw_generic_step, chain_generic_fold + generic per-kind/restart/concat-line lemmas targeting fold_nquads_acc). 430 new lines vs the ~670 estimate — the low-level shift lemmas needed zero duplication, as predicted. ALSO CLOSED first-attempt: lw_ds_step_extend_right via its FINDING's recorded fix (match result stated as its own trivial lemma). Nothing attempted was left unclosed; the superseded FINDING got a forward-pointer (rule #14). Streaming program end state: parser theorem + dataset consumer + generic consumer all proved; sole residue = the ^^datatype literal-branch shift lemma (does not gate any landed theorem). No admits, no --lax, no new assume vals.

#402 residue CLOSED: datatype literal branch (2026-08-11, branch dt-branch): lemma_parse_literal_datatype_shift verifies. ROOT CAUSE of the 3 prior failures (findings discipline vindicated again, same class as Axioms fact 6): the statement was FALSE as written — dt free over wf_iri includes rdf_lang_string/rdf_dir_lang_string, where literal_wf fails and parse_literal returns ParseFail on BOTH sides; the ensures only covered ParseOk/ParseOk. Z3 was correctly refusing a false goal for three sessions; the old FINDING blamed string-equality trigger behavior — WRONG, now corrected in the banner (rule #14). With dt <> rdf_lang_string /\ dt <> rdf_dir_lang_string added to requires, the proof is 3 lines, same shape and budget as the plain-literal sibling. Witness-record hypotheses per the streaming pattern. #402's residue list is now EMPTY — every named theorem and lemma of the streaming program is proved.

#401 M1 triple-composition checkpoints (2026-08-11, branch triple-roundtrip, agent died mid-run — branch harvested): two verified commits landed before the agent's death: bracket-embedding prerequisites + a single-space pws lemma, and bare-IRI + subject-wrapped IRI round-trip lemmas (RDF.NTriples.RoundTrip.fst). Re-verified clean on the merged tree (VERIFY_RC=0). The full arbitrary-triple composition (subject-predicate-object chaining to parse_triple) was NOT reached — remainder queued for a narrower re-dispatch. No admits, no --lax, no new assume vals.

#381 FIXED: XML normalization (2026-08-11, branch xml-norm): both XML 1.0 spec divergences closed in Parser.XML.fst as pre-passes — normalize_line_endings (§2.11, byte-level scan, safe because CR/LF are single-byte ASCII never appearing in UTF-8 continuations) at all three document entry points, and normalize_attr_literal_ws (§3.3.3) applied ONLY to raw literal runs, never to decoded char-ref output — the literal-vs-reference distinction ( stays a tab; a literal tab becomes a space) holds by construction, confirmed by the control fixture staying byte-identical. Parser.XML + 11 dependents verify clean. Gates: both DIVERGES fixtures flipped to WORKS; xmlconf 1447 pass, 0 fail, 1138 skip (of 2585) — identical to baseline; SPARQL 631/0; RDF 1031/0 incl. rdf-xml 166/0. The RDF/XML proof program's normalization precondition is now MET. SIDE FINDING (likely the #367 root cause): w3c_runner's rif_rules_path_for uses a hardcoded relative path with no repo-root fallback — running from ocaml-output/ instead of repo root produces EXACTLY 4 spurious RIF-entailment failures; from repo root, 631/0 and 70/0. Follow-up issue filed for the runner path resolution.

#324 SE-1 FIXED: simple_entails literal check (2026-08-11, branch simple-entails): the loose literal_eq (case-folded language tags; XML-canon-equated rdf:XMLLiterals) made simple_entails accept non-entailed pairs — machine-checked witness stood in-code. Fixed: new strict literal_term_eq (field-by-field; no folding, no canon), simple_entails switched to it. Regression pins BOTH directions (old accepted pair now REJECTED; real matches still accepted, per the vacuity discipline). BONUS: simple_entails_sound_ground — soundness with NO literal-content side condition for bnode-free graphs, first attempt; the general theorem keeps graph_exact because one smaller path (shared bnode binding twice across tag-case/XML-form-differing literals) still uses the loose check — documented in-code. Gates: 26 rdf-mt-layer modules verify; RDF 1031/0 (rdf-mt 39/0 — unchanged because the runner does not call this F* function for the simple regime, SE-3 wiring tracked on #324); SPARQL 631/0. NOTE: binary artifacts on the work branch now mix two branch builds (one .cmi conflict resolved arbitrarily) — a reconciling extract+compile on the merged tree follows the Turtle (#334) harvest.

Runner truthfulness batch (2026-08-11, branch runner-truth): (1) #418 FIXED — rif_tc_base now uses the runner's standard search-list; identity verified: SPARQL 631 pass, 0 fail (of 631) and RDF 1031 pass, 0 fail (of 1031) from BOTH repo root and ocaml-output/, all four RIF tests passing from both. The #367 intermittency mechanism is retired. (2) SE-3 DONE — the runner's "simple" regime now dispatches to the F*-extracted RDF_Entailment_Simple.simple_entails (rule-#15 violation removed); verified by a true before/after binary diff: rdf-mt 39/0 → 39/0, RDF 1031/0 → 1031/0, zero flips — the score now comes from the VERIFIED function. (3) #333 sharpened: all 10 vacuous passes confirmed; they return Pass before any entailment function runs (result_file None), and the real fix is datatype-clash/ill-formed- literal detection under D-entailment in the F* engine (iron rule #1), not runner wiring — scoped as #333's follow-up. BONUS: nine modules listed in build-ocaml.sh but never extracted/committed (incl. RDFS.Closure.SemiNaive — the known full-js-rebuild blocker) are now in ocaml-output/, all F*-verified clean, no patches needed.

#365 EX-1/EX-2 ALIGNED (2026-08-14, branch ex-align, owner decision 2026-08-11 verbatim "Align"): the SPARQL expression evaluator's boolean layer is now error-aware, closing both divergences the G4/M2 wave-1 agent found in SPARQL11.Expression.Refinement.fst. EX-1: EBV (§17.2.2) now signals a Type Error for any value outside the table (xsd:boolean, numerics, xsd:string/simple literals only) — a non-empty rdf:langString is no longer truthy. EX-2: E_And/E_Or/E_Not (§17.3) are now error-tolerant AND error-preserving — a determinate false/true operand still dominates an erroring co-operand, but every other combination now signals ER_Error instead of silently folding to a definite bool. New engine functions in SPARQL11.Algebra.fst: ebv_checked : eval_result -> option bool (the table, None = Type Error; ebv is now defined via it, folding None to false so every FILTER/HAVING/E_If call site keeps its "Type Error drops like false" contract unchanged) and bool_and_checked/bool_or_checked/ bool_not_checked : option bool -> option bool (§17.3's tables, literal copies of SPARQL11.Expression.Refinement.fst's independently- authored spec_and/spec_or/spec_not). The four pre-landing FINDING (divergence) lemmas RETIRE into AGREEMENT lemmas of the same root name: lemma_ebv_langstring_finding → lemma_ebv_langstring_agrees; lemma_eval_and_true_error_diverges_finding → lemma_eval_and_true_error_agrees; lemma_eval_or_false_error_diverges_finding → lemma_eval_or_false_error_agrees; lemma_eval_not_error_diverges_finding → lemma_eval_not_error_agrees — same witnesses, now provably matching the engine instead of diverging from it. The expr-level corollaries (lemma_eval_and_matches_spec/_or_/_not_matches_spec) are also strengthened from a Some b-hypothesis-carrying form to full unconditional agreement (every eval_expr_with_base outcome, error case included). SPARQL11.Algebra.Refinement.fst's ~74-case congruence lemma (lemma_eval_expr_congr) needed no change: its E_And/E_Or/E_Not cases only recurse into sub-expressions and let SMT function-congruence carry the result through whatever pure combinator the arm uses, so the new error-aware combinators verify under the same proof shape. Gates (2026-08-14, branch ex-align): 229 of 230 F* modules verify clean (z3 4.13.3, no admits, no --lax) — the one exception, RDF.CottasStore.PageCache.Bounds.fst, is a pre-existing failure with zero dependency on this change (untouched by the diff, filed separately as #422); SPARQL W3C 631 pass, 0 fail (of 631); RDF W3C 1031 pass, 0 fail (of 1031) — no test flipped. The new BIND/SELECT- expression regression (tests/local/cli_ex_align_regressions.sh, run through the real CLI backend path, not just the W3C runner's pure algebra path) is 14 pass, 0 fail (of 14). FILTER/HAVING semantics are unchanged by construction (Type Error still drops a row/group exactly like false) except the one FILTER-visible flip EX-1 itself causes: a bare non-empty langString literal in FILTER position used to keep the row and now drops it, per the corrected EBV table — pinned in the same regression script.

#334 FIXED: Turtle silent drops (2026-08-11, branch turtle-strict2): an undeclared prefix made Parser.Turtle.fst discard the statement and return SUCCESS — silent data loss (measured before the fix: factoidal dump on a 3-statement file exited 0 and printed 2 triples, no message). Fix: turtle_doc_result gains a tdr_error field carrying the message + byte position (the information was already computed, then thrown away); two new entry points parse_turtle_diagnostic / parse_turtle_with_base_diagnostic surface it through the parser's OWN existing ParseOk/ParseFail convention (no new error mechanism); the CLI's Turtle load path exits 1 and prints message + position to stderr. Gates: regression 6 pass, 0 fail (of 6) — was 2 pass, 4 fail before the fix; W3C RDF 1031 pass, 0 fail (of 1031); SPARQL 631 pass, 0 fail (of 631); nothing flipped, so no W3C test had been passing BECAUSE of the drop. This retires the first half of the silent-drop class named in the streaming plan's constraint 4. SCOPE (follow-ups on #334): Turtle Mode 1.2 (--rdf12), TriG, and the manifest loader may carry the same defect — untouched here.

#334 FOLLOW-UPS FIXED: Turtle Mode 1.2, TriG, manifest loader (2026-08-14, branch silent-drops2): all three follow-ups named above confirmed present and fixed, same mechanism as the RDF 1.1 Turtle fix.

Gates (all three, from repo root, native binaries): W3C RDF 1031 pass, 0 fail (of 1031); SPARQL 631 pass, 0 fail (of 631) — both unchanged from baseline, nothing flipped. This retires the #334 follow-up scope in full. SPARQL and JSON-LD context paths (also named in #334's task list as "check whether … share the lenient-drop shape") are NOT covered by this landing — out of scope for the branch that did this work.

#16 DIFFERENTIAL TESTING vs Apache Jena (2026-08-11, branch diff-testing): the missing independent cross-check now exists. tests/unit/run-jena-diff.sh (opt-in, skip-with-reason when Jena is absent) runs the vendored W3C Turtle + N-Triples corpus (389 files) through BOTH factoidal and Jena 6.2.0 riot/rdfcompare and compares. Only ONE normalization: blank-node labels compared by graph isomorphism (implementations may name them freely); literals, datatypes, language tags and IRI forms compared EXACTLY, so a "disagree" is a real disagreement. Result: 261 agree-parse, 120 agree-reject, 2 disagree, 6 either-side-error (of 389). HARNESS BUG CAUGHT BEFORE TRUSTING ANY RESULT: the first comparison target (factoidal-dump-nq) silently drops bad input — a best-effort dump, not the strict parser the W3C suite grades; 120 files looked like disagreements that were not. Fixed by adding --strict routing through the conformance parser entry points (bin/ consumer change), plus a stale module list in build-ocaml-serializer.sh. TWO REAL PARSER BUGS FOUND (#425), both in the Turtle number-literal classifier: leading- dot numbers without exponent (.1, +.7) are tagged xsd:double but the grammar says DECIMAL; 123.E+1 and -.2e3 are REJECTED but are legal DOUBLE. Our own W3C Turtle suite reportedly scores 100% on these four files — so the manifest expectations may ALSO be wrong; that check is open on #425. Adjudicated NOT a bug: 4 turtle-eval-bad cases where we reject illegal raw IRI characters and Jena only warns — we are the more conformant side.

#425 FIXED: Turtle number-literal classifier + the false-100% mechanism (2026-08-11, branch ttl-numbers): both bugs the Jena differential harness found are repaired in Parser.Turtle.fst:parse_numeric_literal. Bug A — a leading-dot number set the has-exponent flag TRUE before scanning any character, so .1/+.7 came out xsd:double instead of DECIMAL (flag initialisation swapped). Bug B — after 123., the scanner only continued past the dot for a DIGIT, so E+1 was left unread and the parse failed; fixed with an is_valid_exponent_at lookahead. WHY OUR SUITE SCORED 100% ON THESE FILES (the finding that matters more than the bugs): W3C positive-syntax tests are graded by ignore (parse_turtle_fstar content (Some base)); Pass — the runner throws the result away and asks only "did it parse", never "is the produced RDF right", and it uses the LENIENT entry point while factoidal dump uses the strict one. So 1031 pass, 0 fail was truthful about "does not crash" and blind to datatype correctness. The manifests are CORRECT — the gap is in the runner's grading logic; filed separately. Gates: regression 20 pass, 0 fail (of 20) — 14 pass, 6 fail before; RDF 1031/0; SPARQL 631/0; JENA HARNESS 265 agree-parse, 120 agree-reject, 0 DISAGREE, 4 either-side-error (of 389) — the 2 disagreements are gone, the 4 remaining are the already-adjudicated turtle-eval-bad cases where we are stricter than Jena. Trap re-confirmed: factoidal-dump-nq is not rebuilt by the main build script and needed its own rebuild for the harness to see the fix.

#333 FIXED: RDFS D-inconsistency detection (2026-08-11, branch dtype-clash): the ten vacuous rdf-mt passes now CHECK. New RDF.Entailment.RDFS.DatatypeClash.fst detects (a) ill-formed literals for recognized datatypes (reusing XSD.Datatypes.literal_ill_formed) and (b) rdfs:range vs value datatype clashes — both gated on the manifest's mf:recognizedDatatypes, matching the rdf-mt rules. Runner's two "no result file, so Pass" branches now DISPATCH to the extracted detector (rule #15 respected — no semantics in the runner). VACUITY EVIDENCE (the point of the exercise): six compile-time assert_norm witnesses — three graphs that MUST be flagged, three that must NOT — plus a live re-run of #333's own corruption trick: garbling the datatypes-range-clash input now flips PASS→FAIL, where before the fix the suite did not notice. A bug in the detector was itself caught this way: the first range-clash version searched only part of the graph, and a should-flag witness refused to verify. SCORES (labelled, and the total DROPS on purpose): rdf-mt 38 pass, 0 fail, 1 unsupported (of 39) — was 39 pass, 0 fail with 10 of them unchecked; full RDF 1030 pass, 0 fail, 1 unsupported (of 1031) — was 1031/0; SPARQL 631/0 unchanged. The 1 unsupported is rdfs-entailment-test001, which needs rdf:XMLLiteral validity checking that this tree does not have (optional in RDF 1.1) — now reported honestly instead of passing falsely. Nine tests match the manifest exactly; no adjudication needed.

#401 M1: IRI round-trip WIDENED to non-ASCII (2026-08-11, branch iri-nonascii): lemma_term_iri_round_trip_utf8 (RDF.NTriples.RoundTrip.fst Part 10) — parse_object of nq_term_to_string (T_IRI i) returns the term for ANY i satisfying is_iri + chars_all is_iri_body_char. The all_ascii hypothesis is GONE. The feared obstacle (multi-byte scan advance) dissolved on inspection: scan_iri_end already reads BYTE-wise, and every forbidden byte (space, angle brackets, quote, braces, pipe, backslash, caret, backtick) is below 0x80, while every byte of a multi-byte UTF-8 sequence is 0x80 or above — so a multi-byte character can never collide with a delimiter (lemma_utf8_enc_char_iri_safe proves by () alone). Remaining work was showing the scan walks a character's own 1-4 bytes: a general step-by-step attempt failed with incomplete-quantifiers; a BOUNDED case split on k=1..4 (UTF-8 is never longer) verified. Bridging: utf8_bytes_singleton added to Parser.FastString.Spec.fst — the ASCII-free twin of utf8_bytes_ascii_singleton — after which the existing general facts carried the build_string bridge through. No rebuild needed and VERIFIED as such: the extracted Parser_FastString_Spec.ml contains ZERO lemmas (checked), so adding a Lemma cannot change the built program. Part 8's ASCII lemma stays for existing callers. Next: widen Part 9's triple-level lemmas the same way.

#429 FIXED: syntax tests graded on the STRICT parser (2026-08-14, branch syntax-grading): four RDF 1.1 positive-syntax test types (Turtle, TriG, N-Triples, N-Quads) were graded ignore (parse ...); Pass — result discarded, LENIENT parser used, so a test passed even when the produced RDF was wrong. Now they use the STRICT entry point, matching what factoidal dump/validate do. AUDIT (docs/designissues/2026-08-14-syntax-grading-audit.md) scoped the damage precisely: RDF 1.1 negative-syntax and eval tests were ALREADY real; RDF 1.2 positive-syntax was already fixed under epic #305; SPARQL query/update syntax tests were already real (one parser, no lenient/strict split); RDF/XML has no such test type. So the vacuity was confined to those four arms. SCORE DROPS ON PURPOSE: RDF 1.1 total 1028 pass, 2 fail, 1 unsupported (of 1031) — was 1030 pass, 0 fail, 1 unsupported. TWO REAL TriG ENGINE BUGS EXPOSED, both reproduced independently against the CLI: #433 (a collection used as a normal SUBJECT is rejected — the graph-name guard over-applies) and #434 (a trailing ; before } with no final . is rejected, though TriG permits it). Jena cross-check unchanged at 0 disagreements (265 agree-parse, 120 agree-reject, 4 either-side-error of 389) — that harness does not cover TriG yet, so it neither confirms nor denies the two new bugs. Unrelated pre-existing gap noted: Math.Sigmoid.fst has source but no committed extracted output on main (same class as the #422 sweep).

#422 FIXED + unlisted-module sweep (2026-08-14, branch cottas-bounds): RDF.CottasStore.PageCache.Bounds.fst had been broken since the Phase 2.5c API change (#118) turned the cache value type from list (option string) into the abstract cottas_column — three of its four lemmas used the old type, one called a pre-migration decoder, one bounded a function the engine no longer calls. Nothing noticed because the module is absent from build-ocaml.sh's list (#327). Re-synced to the current API, verifies clean, and ADDED to ALL_MODULES (proof-only: no compile-list entry needed). SWEEP RESULT: 26 files were unlisted; 9 are .fsti paired with a listed .fst, 2 are interface-only but covered through listed consumers, and 15 are real .fst content — all 15 verify clean, 0 fail (of 15). Measurement honesty note: a 300s-per-module cap first reported SPARQL11.Algebra.BGPRefinement.fst as FAILING; an unhurried retry verified it in 5m15s — slow, not broken, and the agent corrected its own number rather than publishing it. 🔴 THE FINDING THAT MATTERS MORE: CI's check-extraction.yml (the PR gate) runs only build-ocaml.sh extract — it NEVER runs make verify. So those 15 modules, which include nearly every theorem landed this session (RDF.NQuads.Streaming, RDF.NTriples.RoundTrip, Parser.NTriples.Locality, SPARQL11.Expression.Refinement, SPARQL11.Parser.TokenRoundTrip, Parser.FastString.Axioms, ...), are guarded by NOTHING in CI. They are correct today; nothing stops a future edit from silently breaking one, exactly as PageCache.Bounds broke. Filed separately.

#433 + #434 FIXED: two TriG parser bugs (2026-08-14, branch trig-bugs): both exposed by the #429 strict-grading fix, both repaired in the PARSER — verified independently that bin/w3c-runner/w3c_runner.ml has a ZERO-line diff, so the score was not recovered by softening the gate. #433: Parser.TriG.fst's "RC3" rule rejected ( at the start of ANY top-level statement to stop a collection being used as a graph name, but fired before subject-position could be distinguished, so it also blocked the legal ( 1 2 3 ) :p ( 4 5 6 ) .. RC3 removed entirely — TriG's grammar makes collection-as-graph-name unreachable anyway (a collection can only be followed by a predicateObjectList, never {), so the case is still rejected, now for the right reason. #434: Parser.Turtle.fst's parse_predicate_object_list_rev / parse_trailing_semicolons_rev treated ./]/;/| as list-terminators but omitted }, so ...;} tried to parse } as a predicate; } (0x7D) added to both checks. SCORES: regression 11 pass, 0 fail (of 11) — was 5 pass, 6 fail; rdf-trig 356 pass, 0 fail — was 354 pass, 2 fail; RDF total back to 1030 pass, 0 fail, 1 unsupported (of 1031) — and that number is now EARNED under strict grading plus real rdf-mt checks, unlike the 1031/0 it replaced. SPARQL 631/0 unchanged. Both modules verify clean, no admits, no --lax, no new assume vals.

#436 FIXED: CI now VERIFIES the proofs (2026-08-14, branch ci-verify): the PR gate ran build-ocaml.sh extract + compile and never make verify, so a proof could break silently — the mechanism behind #422. Happy discovery: formal/fstar/Makefile's verify target already globs $(wildcard *.fst) from DISK, so it covers all 231 files including the 15 absent from ALL_MODULES (216 entries) — it was simply never connected to CI. New workflow .github/workflows/verify-fstar.yml runs make -j verify under z3 4.13.3, no --lax, and FAILS the PR on any verification failure. Caching: its own GitHub Actions cache key for the .checked files, deliberately NOT shared with the other workflows (theirs cache only the 216-module list, which would leave the 15 cold every run) and deliberately NOT the checked-cache git branch (that is a local session-restore mechanism, not a gate input). BOTH DIRECTIONS VALIDATED — the point, given this session's vacuity findings: clean tree PASSES (exit 0, all 231 files, 22.7 min on 4 cores from a half-warm cache), and a deliberately falsified lemma in a scratch copy FAILS (exit 2, "Could not prove post-condition"). A gate that cannot fail is the same trap in another costume. Honest gap: a fully cold run is estimated 45-60+ min and was not measured; it happens on first run and after F* version changes. Coverage was NOT narrowed to make the job fast.

#430 FIXED + two coverage findings (2026-08-14, branch vacuity-tool): tools/negative-test-vacuity.py crashed with TypeError: int + str — a KEY CLASH: the per-suite dict uses "error" for a COUNT, while the whole-manifest-load-failure early return used the same key for a MESSAGE. Renamed to manifest_error; added a "suites that could not be loaded" table and a stderr warning, because before this a failed load reported 0 across every count, indistinguishable from "this suite has no negative tests". Tool now runs to completion. VALIDATED BOTH WAYS: emptying a premise to zero triples flips a real test's verdict to vacuous/closure-adds-nothing; the current run's 3-of-19 rdf-mt vacuous count matches the last known-good state recorded in skills/measuring-inference. 🔴 FINDING 1 — the tool is BLIND to the #429 class and its in-code justification is WRONG about why: it excludes positive-syntax tests reasoning that "a reject-all parser fails these", but #429's actual bug was IGNORE-AND-PASS, which that argument does not cover. FINDING 2 — its vacuous verdict for 3 mf:result false tests is correct for the CLI path it measures but no longer describes the graded suite: #333's D-inconsistency detector lives in the runner's dispatch and is unreachable via factoidal entail/--dump. Also: rdf12-semantics cannot be audited at all because its vendored manifest fails to load on the SAME undeclared test: prefix typo the #334 work found at rdf-semantics/manifest.ttl:247 — one upstream typo now blocks two tools.

#443 + #339 + #445 + #446 FIXED (2026-08-14/15): three serialization defects (plus two latent siblings of #445 found while fixing it), none of which any W3C suite could see, plus one build-gate failure found on the way. #445 landed 2026-08-15 on branch utf8-store; the other three landed 2026-08-14 on branch claude/autoexec-scratchpad-assess-37oeok.

🔴 #443 (with #339) — the store DESTROYED literals. An RDF literal whose lexical form held ", LF or \ did not survive import -> query: it returned as _:cottas_decode_oor. Three of six literal classes lost. Cause: RDF.Pretty.term_to_ntriples wrote lexical forms verbatim. Documented in two banners as "display, not wire" — while all THREE of its callers were wire paths (--dump, the COTTAS object column, the same column in the npm build). The --dump half was filed in July as #339 and pinned XFAIL; its scope table listed the other SERIALIZERS and concluded "one function, not a systemic gap" — correct about serializers, and the missing column was the other CALLERS. DELETED rather than fixed: an escaping version is byte-identical to RDF.NQuads.Serialize.nq_term_to_string, and a second name for one rendering is what let them drift. Trust-surface finding for this registry: RDF.NTriples.RoundTrip.fst was SOUND throughout and simply did not cover the function the CLI called. Proof coverage is a property of the WIRING, not only of the statement — the same shape as the vacuity findings (#333, #429). Recorded as hazard #25.

✅ #445 — the store corrupted ALL non-ASCII, FIXED 2026-08-15 (branch utf8-store). Found while validating #443, by reading the store bytes rather than the result table. RDF.Bytes.bytes_of_string = String.list_of_string yielded CODEPOINTS; each was written as codepoint land 0xFF. Verified against the stored bytes in four scripts: café→caf\xe9, λόγος→\xbb\xcc\xb3\xbf\xc2, 日本語→ \xe5,\x9e, 🎉→\x89. Note the middle one: U+672C truncated to 0x2C, a literal COMMA inside a stored token — this could change how a token PARSED, not only what it said. A second defect on the same path: BaseWriter.fst:177 wrote the length prefix as write_uvarint (String.length s), and F*'s String.length counts codepoints while the payload is bytes; the two coincide for ASCII, which is why nothing noticed. The property that would have caught all of it — bytes_to_string (bytes_of_string s) == s for arbitrary s — is now PROVEN, unconditionally (RDF.Bytes.lemma_bytes_to_string_of_bytes_of_string), built on Parser.FastString.Spec.utf8_decode_all_utf8_bytes_identity (landed 2026-08-11, itself once listed "parked" in that module's own banner — the fix this issue needed had already landed one layer down, just not wired up here yet). RDF.Bytes.byte is now a REFINED type (c:FStar.Char.char{int_of_char c < 256}), so the old bytes_of_string = String.list_of_string no longer typechecks — the type now enforces the invariant its comment always claimed. Beyond the three sites this issue named, the same audit found and fixed a fourth live site in BaseWriter.fst (write_dict_entry, the RLE_DICTIONARY path — live for real object literals, not just metadata) and a fifth, string_lengths, feeding the DLBA length block that is the PRIMARY value-storage path for all four columns in the v1 writer. Fixing RDF.Bytes also turned up the identical latent defect, self-consistent only under the old wrong bytes_of_string, in two sibling modules that share its primitives: RDF.Store.Columnar.DeltaLog.fst (the durable- UPDATE delta log — LIVE) and RDF.CottasStore.DictWriter.fst (a legacy .dict writer with zero current callers) — both surfaced as build failures, not by inspection, and both fixed the same way (field_byte_len/tok_byte_len helpers replacing every String.length used as a byte length or well-formedness bound). Owner decision (2026-08-15, verbatim: "Version-bump the COTTAS header - nobody is using our software yet except me... I can nuke and rebuild it"): the COTTAS base file's FileMetaData.version field is now stamped 445 by the writer and REJECTED by the reader if it doesn't match — no migration path, no silent misread of a pre-fix store. The XFAIL entry (UTF8-STORE in tests/known-defects/run.sh) is retired; the standing regression pin moved to tests/local/cli_literal_escape_roundtrip.sh.

✅ #446 — xmlns:= malformed default namespace. Found by the Jena differential harness on its FIRST RDF/XML run. render_ns_decls emitted xmlns:<p>="<u>" for every declaration; the default namespace has an empty prefix, so it wrote xmlns:="...", which is not a legal XML attribute name. Both witness files are commented OUT of the vendored manifest, so w3c_runner --rdf rdf-xml never discovers them and 166 pass, 0 fail was accurate while this was live. That is the argument for differential testing in one sentence: a second engine reached an input our own manifest excludes. Fixed by a case split in the render step. Open follow-up recorded on the issue: we hoist a VISIBLY-USED default namespace to the root, Jena attaches it to the element that uses it; the manifest's "implementation dependent" carve-out covers only NOT-visibly-used namespaces, so placement is outside it and wants a read of exc-c14n §2.

✅ #444 — Parser.XML was a marginal proof. A full extract went red on a module with no change to it: parse_attributes's fuel - 1 under an if fuel = 0 guard failed the subtyping check. Four causes ruled out first, each of which would have been a real regression (the #443 change: outside its cone; CPU contention: reproduces alone; a stale Parser.FastString.fsti.checked: regenerated, still red; the utf8_bytes_singleton lemma landed the same day: reverted Spec.fst to its parent, still red). Verifies at 60. Scoped #push-options around that one function, no --lax, no --admit_smt_queries. A proof this close to the line is a latent CI flake that fails with text reading like a semantic regression; sweeping the corpus at a REDUCED rlimit would find the rest before they fire.

Jena differential harness extended to five formats (from #317). Labelled counts: turtle 317 files — 222 agree-parse, 91 agree-reject, 0 disagree, 4 either-side-error; ntriples 72 — 43/29/0/0; trig 357 — 242/111/0/4; nquads 89 — 55/34/0/0; rdfxml 173 — 128/31/4/10. Combined 1008 files — 690 agree-parse, 296 agree-reject, 4 disagree, 18 either-side-error. Every non-agreement adjudicated: 13 are inputs the W3C test says must fail and we correctly reject while Jena accepts; 2 are files where OUR output matches the official expected .nt and Jena's does not; 2 are #446; 5 are manifest-disabled. The harness was PROVEN able to fail three ways (reinstated #434 bug -> either-side-error; one-value difference -> disagree; extra named graph -> caught). Its declared blind spot: blank-node GRAPH names are pooled per side, so content moving between two such graphs can hide (3 of 357 TriG files).

Suite state after all of the above, re-measured on the rebuilt binary: SPARQL 1.1 631 pass, 0 fail (of 631); RDF 1.1 1030 pass, 0 fail, 1 unsupported (of 1031), rdf-xml 166 pass, 0 fail; RDF 1.2 242 pass, 0 fail (of 242); RDF 1.2 c14n 82 pass, 0 fail (of 82); RDF 1.2 Semantics 41 pass, 3 fail, 3 skip (of 47, pre-existing and already documented under #305 P9); SPARQL 1.2 254 pass, 0 fail (of 254). Every one byte-identical to its committed baseline — the serializer change moved no score. OWL catalogs NOT re-measured: the run was capped at 3000s and semantics-direct alone budgets 9000s, so the partial logs were reverted rather than committed as scores.

#362 SR-4 FIXED (2026-08-16, branch sr4-order): sparql_order read a numeric parse failure as a universal tie, breaking transitivity — one ill-typed literal silently misordered the VALID numerics around it (witness: 10, zzz, 3, 5, abc, plain). Owner-adjudicated rule, measured against Jena 6.2.0: valid numerics numeric, ill-typed AFTER them, lexical tie-break among ill-typed (datatype ignored). New sparql_order_numeric (SPARQL11.Algebra.fst:5172); numeric_compare unchanged for its other callers. Proofs in SPARQL11.Algebra.Refinement.fst: lemma_sparql_order_numeric_frag_totality

#448 wave 1, module 1: Parquet.Footer lifted merely-tot -> algorithm-correctness (2026-08-16, branch assure-parquet-footer): lemma_version_field_roundtrip (RDF.CottasStore.BaseWriter.fst:1272) proves the on-disk READER returns what the WRITER wrote for the FileMetaData version field: parse_file_metadata_version_hex (bytes_to_hex (write_field_i32 1 0 cottas_format_version)) == Some cottas_format_version. This is the fact the #445 format gate depends on — nothing previously forced writer and prober to agree, and a disagreement would have made the gate reject every store or accept every store, silently. Proved for the DEPLOYED value 445, not general v (needs induction over the varint writer's recursion — documented in-code as follow-on, and the narrow lemma is a true fact about the shipping format, not a weakened general one). The decode logic was refactored out of the probe function so the lemma names the EXACT function the reader calls, not a copy. No admits, no --lax. Twelve helper lemmas; F* needed three explicit congruence helpers to chain rewriting steps (recorded in the lemma's comment). CLI pin parquet_footer_version_gate_roundtrip.sh (3 pass, 0 fail of 3) covers the file-I/O step the lemma cannot reach: real store via the real CLI, then one byte flipped on disk must reject. Tier verified by running the actual classifier, not asserted. 🔴 FINDING (rule #11): parquet_read_tail_hex/parquet_read_range_hex realisations carry an OCaml hex-encode step (__mim2_hex_encode) with no F* spec twin and no hash-witness test — byte-layout logic in glue; follow-up owed. Gates: RDF 1030 pass, 0 fail, 1 unsupported (of 1031); escape pin 5 pass, 0 fail; unit suite 20 pass, 28 fail (of 48) = baseline exactly. 📊 #448 baseline moves 129 -> 128 merely-tot (of 231).

#448 wave 1, module 2: HDT.Container lifted merely-tot -> algorithm-correctness (2026-08-16, branch assure-hdt-container): HDT.Container is a pure READER (container skeleton framing over the HDT v1 binary format -- cookie/format/props/CRC16 control-info blocks, PFC/log-array/bitmap section-boundary arithmetic), no writer side, so the writer/reader round-trip template (module 1's pattern) does not apply. Chose instead the "only reads" template: prove corruption is REFUSED, not silently decoded as noise. Two lemmas: lemma_parse_control_info_rejects_bad_cookie -- a mismatched 4-byte $HDT cookie makes parse_control_info return None, proved by direct unfolding (()), no induction, no congruence bridging needed (unlike module 1's version-field round trip, every hypothesis here is a bare byte_get equality feeding a match the SMT encoding resolves directly). lemma_bad_global_cookie_rejects_container is the corollary at the shipping entry point every consumer actually calls: a corrupted Global cookie at file offset 0 fails the WHOLE hdt_parse_inventory_hex, not just the Global control-info block, by forwarding parse_control_info a 0's None verbatim. Both relate two named shipping functions (parse_control_info / hdt_parse_inventory_hex, plus byte_get) with no declarative relation, so the classifier reads algorithm-correctness. pfc_type refined from bare nat to (t:nat{t = 2}) per the #445 template -- was a comment-only invariant ("2 = Plain Front Coding"). Assume-val audit: HDT.Container itself carries ZERO assume vals -- all file bytes cross the boundary through Parquet.Footer's parquet_read_range_hex, already audited under module 1's finding above (nothing new here). CLI pin: bin/hdt-probe/check.sh gained a corrupted-global-cookie arm (flip byte 0 of a real vendored .hdt fixture, 0x24 -> 0x25, require the probe's loud PARSE FAILED and rc=1) alongside the pre-existing truncation arms; 75 pass, 0 fail (of 75) end to end, plus tests/local/hdt_stage4_parity.sh (backend parity against the same fixture) at 6 pass, 0 fail (of 6), both unaffected by the refinement or the new lemmas since the shipping functions' extracted behaviour is byte-identical. Tier verified by running the actual classifier, not asserted. Gates: RDF 1030 pass, 0 fail, 1 unsupported (of 1031); escape pin 5 pass, 0 fail (of 5); unit suite 20 pass, 28 fail (of 48) = baseline exactly.

#448 wave 1, module 3: RDF.Turtle.Serialize lifted merely-tot -> internal-refinement (2026-08-16, branch assure-turtle-serialize): new declarative relation compacts_to_pname_safe + lemma lemma_ts_abbreviate_iri_pname_safe — the IRI-abbreviation step either emits the full <iri> form or the compacted local name is PN_LOCAL-safe per the Turtle grammar. No admits. ESCAPING QUESTION ANSWERED: the module SHARES RDF.NQuads.Serialize.nq_escape_literal — no second escaper exists, so no #443-shape divergence risk (checked, not assumed). FINDING, verified by experiment: the term-level round-trip nq_escape_literal "a" == "a" is NOT provable by normalization — Error 19, the same computation wall RDF.NTriples.RoundTrip.fst hit, STILL present after the 2026-08-10 change intended to clear it; assert_norm witness batteries hit the same wall. What proof cannot reach, the pin covers: turtle_pretty_serialize_roundtrip.sh (4 pass, 0 fail of 4) re-reads --dump-turtle output over the 10-literal fixture with an anti-vacuity arm. Zero assume vals. Tier from a fresh classifier run WITH --json. Gates: RDF 1030 pass, 0 fail, 1 unsupported (of 1031); escape pin 5 pass, 0 fail; turtle_pretty_regressions 17 pass, 0 fail (of 17); unit suite at baseline.

#448 wave 1, module 4 (last of wave 1): RDF.Canonical lifted merely-tot -> internal-refinement (2026-08-16, branch assure-rdf-canonical): new declarative relation is_issuer_label (existential: label == prefix ^ nat_to_string n) + issuer_labels_wf

#448 wave 2, module 1: Parser.BallyhooCOTTAS audited 17-of-17; 4 assume vals LIFTED to F (2026-08-16, branch assure-ballyhoo-cottas)*: cottas_lookup_named_graph, cottas_estimate, cottas_predicate_present_in_graph, cottas_graph_candidates_for_predicate are now real F* code — each was a pure derivation of other store operations with no I/O of its own. cottas_estimate = length (cottas_search ds bound) makes an invariant F*-visible that previously lived only in glue ("estimate is an exact count, never a heuristic"). Module assume-vals 17 -> 13; all 13 remaining are rule-#11(b) delegation (file I/O + hashtable lookups over data resolved at open). Tier stays merely-tot and that verdict is HONEST — boundary glue with no theorem to state beyond totality. 🔴 MAIN FINDING: cottas_runtime.sh:93-184 hand-parses RDF term syntax in OCaml (find_unescaped_quote / unescape_literal / parse_literal_token / parse_subject...) — iron rule #4 violation, not commit-sized, needs an F*-side parse_stored_term + column-decode contract change. 🟡 minor: cottas_search re-implements the 3-way graph-bound semantics its .fst doc specifies; build_summary hardcodes CE_Delta. FIXED IN PASSING (pre-existing): the glue's stub-deletion anchors matched type-qualified signatures that fresh extraction now prints differently (RDF_Graph_Executable.subject -> RDF_Term.subject), so 10 raw stubs survived and would have SHADOWED the real implementations on any from-scratch rebuild — re-anchored on stable failwith text. FIXTURE REGENERATED: store_capabilities_sample .cottas was pre-#445 format and the version gate now (correctly) rejects it; rebuilt from its documented source (tests/local/data/cottas_sample.nq) with the current writer; smoketest opens it (5 quads, 1 row group). Gates: RDF 1030 pass, 0 fail, 1 unsupported (of 1031); escape pin 5 pass, 0 fail; footer pin 3 pass, 0 fail; unit suite at baseline. Project assume-val total 133 -> 129.

#448 wave 2, module 2: Parser.BallyhooHDTQ audited 17-of-17; 4 assume vals LIFTED to F (2026-08-16, branch assure-ballyhoo-hdtq)*: hdtq_lookup_named_graph, hdtq_estimate, hdtq_predicate_present_in_graph, hdtq_graph_candidates_for_predicate are now real F* code — the same four names, same derivations, as module 1's COTTAS lift (hdtq_estimate = length (hdtq_search ds bound), etc.). Module assume-vals 17 -> 13; all 13 remaining are rule-#11(b) delegation placeholders. 🔴 MAIN FINDING, different in kind from module 1's: this module is DEAD END TO END. grep -rl "hdtq_" experimental_ocaml_glue/*.sh returns nothing — there is no glue script at all (not hand-parsed terms like #454's COTTAS finding; simply absent), so every one of the 13 remaining assume vals extracts as a raw failwith "Not yet implemented" stub, and no other .fst file calls any hdtq_* function (only open Parser.BallyhooHDT for its TYPES, never its functions). Contrast COTTAS: RDF.CottasStore.fst and RDF.Store.Loader.fst call into it, so it is live. Per #448's own per-module deliverable ("state ONE correctness property the module's consumers rely on ... a module where no property can even be stated is a finding, not a skip") — this module's finding IS that sentence: it has no consumers to state a property about. 🟡 secondary finding: hdtq_close_dataset_store (assumed, Tot unit, unreferenced) has NO extracted OCaml symbol at all — F* elides an unused, unrealised, unit-returning assume val rather than stubbing it (same elision Parser.BallyhooHDT.fst's defined hdt_close_graph_store = () gets, for the same reason). A future caller would hit a link error, not a controlled runtime failwith. Sibling question answered: Parser.BallyhooHDT carries 0 assume vals as of its 2026-07-06 stage 4 landing and needs no further wave-2 pass; BallyhooHDTQ does not call into its already-verified per-graph reader today, which is a design opportunity for whoever wires this module up, not an audit action. No stub-anchor staleness (recurrence check 2): N/A, there are no anchors to go stale. Forced extract --force-full; every hdtq_* symbol appears exactly once in the fresh .ml. Tier from a fresh classifier run WITH --json: "assurance_tier": "merely-tot", "merely_tot": true, "assume_val_active": 13 (assume_other_active: 1 for the opaque hdtq_handle type) — HONEST, boundary glue that doesn't exist yet has no theorem to state. Gates: RDF 1030 pass, 0 fail, 1 unsupported (of 1031); hdt-probe 75 pass, 0 fail (of 75); hdt-stage4-parity 6 pass, 0 fail (of 6); escape pin 5 pass, 0 fail (of 5); unit suite 20 pass, 28 fail (of 48) = baseline exactly, no deltas. Project assume-val total (active, per classifier): 125.

#448 wave 2, module 3: RDF.CottasStore.OnDiskRuntime audited 15-of-15; 0 lifted, 0 fixed — module is DEAD END TO END despite a CLEAN realising glue (2026-08-16, branch assure-ondisk-runtime): 🔴 MAIN FINDING, contradicts this module's own dispatch brief ("the LIVE on-disk query runtime... traversed by every --data-cottas query"): it is not live. grep -rn "OnDiskRuntime\." formal/fstar/ *.fst outside the module's own file returns nothing — no other .fst calls any ondisk_*_indexed / ondisk_*_via_registry function, and the LazyDictRegistry it reads through (RDF.CottasStore. LazyDictRegistry.fst) has no consumer outside this module either. The production --data-cottas path (RDF.Store.Capabilities.Cottas. fst's caps_of_cottas) runs entirely through the token-shaped cottas_ondisk_search_tok / _estimate_tok / _count_exact_tok family in RDF.CottasStore.fst — real Tot functions, no assume vals — per that module's own 2026-07-06 comment: the id-based dictionary path was retired from the hot path in favour of direct term<->token serialisation. The design doc this module cites (docs/designissues/2026-05-13-issue-118-cottas-ondisk-runtime- retirement-plan.md) already flagged this as "partially overtaken by events" and named three non-production consumers still on the id-based path (a tests/unit baseline, cottas_ondisk_smoketest, factoidal-explain) — re-checked here and ALL THREE have since migrated to _tok calls; none reference OnDiskRuntime or ondisk_*_indexed/_via_registry today, so even that residual consumer list is stale. The .github/test-suites/local-cottas- corpus.yaml "direct-trigger" association the classifier surfaces is a file-glob CI trigger-path listing (any RDF.CottasStore*.fst change re-runs the suite), not evidence of exercise — verified by reading the glob, not by suite content. 🧭 decision needed from owner/maintainer, not inferred here: retire (delete the 15 assume vals + cottas_ondisk_runtime_indexed.sh + the orphaned LazyDict/ LazyDictRegistry chain, ~2 commit-sized module 4/5 follow-ups) vs. keep as scaffolding for the file's own noted "Future work (Phase 2.5h) re-introduces ondisk_search_indexed... once a perf-fast variant lands again." Hazard 1 (hand-parsed RDF terms): CONFIRMED, same site as #454, reached only if this dead path is ever wired up — parse_iri_token / parse_literal_token / parse_subject_str / parse_object_str (cottas_ondisk_runtime.sh:126-215) populate the Hashtbl tables that ondisk_*_indexed's realising glue (cottas_ondisk_runtime_indexed.sh) forwards to; per the brief, not fixed here — filed as the same finding, second reachability path, not a new site. Brief named cottas_ondisk_runtime.sh as this module's glue; the module's actual realiser is the separate, smaller cottas_ondisk_runtime_indexed.sh (161 lines) — a clean rule-#11(b) dispatch shim with no parsing/byte-layout logic of its own; it calls INTO cottas_ondisk_runtime.sh's Hashtbl runtime for 8 of the 15, and into RDF.CottasStore.LazyDict/LazyDictRegistry (separate modules' own assume vals) for the other 7 — corrected here, not silently substituted. Hazard 2 (stale stub-deletion anchors): NOT FOUND — forced rm + fresh single-module extraction, 15/15 stubs present exactly once pre-patch, all 15 anchors matched (subn count 1, zero WARN) post-patch, zero leftover failwith "Not yet implemented" text, 15 unique let ondisk_* bindings post-patch, and the freshly regenerated .ml is byte-identical to the committed one (git diff empty) — this glue's anchors are healthy, unlike module 1's sibling. Lifts: NONE — the 8 indexed vals are asymmetric encode(token->id)/decode(id->token) pairs over two different Hashtbl directions built once at open time; there is no pure relationship between them expressible without the ML-effected Hashtbl read itself (unlike modules 1/2's estimate = length(search) shape), and the module has no _estimate/_search pair at all (comment confirms: those were retired to F* separately, before this module existed). Type refinements: NONE — no comment in the module claims an invariant its option/nat signatures don't already state (id range and path-registration are already option-typed absences, not stronger claims). Trace logging ([qof3-trace]/[bet7-trace]): purely additive Printf.eprintf diagnostics in both glue files; none gate control flow — confirmed by reading every call site. Tier from a fresh classifier run WITH --json: "assurance_tier": "merely-tot", "merely_tot": true, "assume_val_active": 15, "foundational_path": false — HONEST for a module with 15 assume vals and 0 theorems, though the tier label alone does not surface the dead-code finding above; that required call-graph grep, not the classifier. No .fst changed, so no extract/compile/gate rerun was needed; targeted make verify-RDF.CottasStore.OnDiskRuntime reverified clean under z3 4.13.3, no --lax. Project assume-val total (active, per classifier): 125, unchanged (no lift landed).

#448 wave 2, module 4: RDF.CottasStore.OnDiskIndex audited 7-of-7; 1 pure property landed (2026-08-16, branch assure-ondisk-index): LIVE, contrary to modules 2/3's dead-end findings — confirmed by grep -rn "RDF_CottasStore_OnDiskIndex\." formal/fstar/ocaml-output/ *.ml: called from RDF_CottasStore.ml (dict_decode_token/ dict_encode_token/read_dict_header/read_presence_header/Vav3_mmap — the on-disk query path), RDF_CottasStore_PresenceBitmap.ml, RDF_CottasStore_CompoundPresenceBitmap.ml, RDF_Store_Columnar_OffsetIndex.ml, RDF_Store_Columnar_SubjectOffsetIndex.ml. RDF_CottasStore is called from bin/factoidal-cli/factoidal_cli.ml, bin/factoidal-http/factoidal_http.ml, bin/npm-entry/entry_jsoo.ml. factoidal_http.ml's prewarm_cottas_columns calls Cottas_companion_boot.prewarm_via_companions at boot (build-or-open the 8 companion files); every on-disk triple-pattern lookup thereafter goes through this module's dict encode/decode + presence bit-test. Both import (lazy companion build on first open) and query (every lookup) shipping paths reach it.

assume val role verdict
mmap_companion_open open+mmap a companion file, return byte length (a) pure I/O
mmap_companion_close release an mmap for a path (d) DEAD — see finding below
read_companion_u32_le LE u32 read from the mmap (a) pure I/O
read_companion_u64_le LE u64 read, with a native-int safety guard (a) pure I/O — type gap, see below
read_companion_byte single-byte read (a) pure I/O
read_companion_string byte-range slice to a string (a) pure I/O
companion_file_size stat-based existence+size, no content read (a) pure I/O

All 7 realise in one glue script, experimental_ocaml_glue/cottas_ondisk_zzzzz_ondisk_index.sh (Vav3_mmap, Unix.openfile + Bigarray.Array1 mmap held per-path for the process lifetime) — rule-#11(a) conformant, no byte-layout or semantic logic in the glue for these 7. 🔴 FINDING: mmap_companion_close is declared and called from 4 sibling F* modules (RDF.CottasStore.PresenceBitmap.close_bitmap, CompoundPresenceBitmap, RDF.Store.Columnar.OffsetIndex, SubjectOffsetIndex — each a one-line mmap_companion_close h.*_path wrapper) but NONE of those 4 wrapper functions themselves have a live caller, so F* extraction drops the entire chain: mmap_companion_close has zero binding in the extracted .ml (confirmed: grep close RDF_CottasStore_OnDiskIndex.ml finds nothing but the reader's own close_mmap/Unix.close glue helper). Not a shipping gap — the glue's own comment says the design is "held for the lifetime of the process," so the never-called close path is intentional, but the assume val is genuinely unreachable at runtime, not merely unrealised.

Lift landed: presence_bit_index_bounded — pure nat-arithmetic lemma, rg < num_rgs /\ tok < num_tokens ==> rg*num_tokens+tok < num_rgs*num_tokens — wired load-bearing into presence_test_bit (called, not decorative), tying the reader's computed bit_index to the capacity the .presence writer sizes its buffer against (write_presence_file's bytes = (rg_count*n+7)/8). Same wave-1 HDT.Container "bounds respected" shape. Verified: make verify-RDF.CottasStore.OnDiskIndex, z3 4.13.3, no --lax, no --admit_smt_queries. Extraction confirms the lemma is proof-only — git diff on the extracted .ml and the rest of formal/fstar/ is empty after a full rebuild; presence_test_bit's extracted body is byte-identical to before the lift (F* erases Lemma-typed calls). 🟡 type-gap finding, NOT landed this pass: read_companion_u64_le's signature is Tot (option nat) (unbounded), but the realisation silently returns None whenever the top byte's high bit is set (b7 >= 0x80, i.e. the on-disk value is >= 2^63) to avoid truncating into OCaml's 63-bit native int — a real realisation constraint the signature doesn't state. Tightening to Tot (option (n:nat{n < pow2 63})) would make it F*-visible; deferred to keep this pass one-commit-sized (would need a second verify→extract→compile→gate cycle after the one already run here).

🔴 SECONDARY FINDING (hazard 2, stale anchor, this module's OWN glue, Step C): the same glue script's later "Step C" block patches decode_subject_fast/decode_object_fast in RDF_CottasStore.ml to add a lazy-parse-on-cache-miss fallback; both anchors WARN "not found" on every build ([vav3-ondisk-index] WARN: decode_subject_fast anchor not found, same for decode_object_fast) because a different, later-running sibling patch (cottas_ondisk_z_lazy_open.sh, 2026-07-19) already restructured both functions by removing the ensure_subjects_loaded/ensure_objects_loaded calls Step C's anchor expects — that same sibling patch's own comment documents WHY: the id-based cottas_ondisk_encode_subject/decode_subject/ encode_object/decode_object F* functions these two OCaml functions served were deleted from RDF.CottasStore.fst in the same 2026-07-19 commit (#254/#118), replaced by the token-shaped _tok family (same retirement wave-2 module 3 found for OnDiskRuntime). Confirmed: decode_subject_fast/decode_object_fast have zero callers anywhere in RDF_CottasStore.ml or bin/ today (grep -n "decode_subject_fast\b" / "decode_object_fast\b" — only their own definitions and stale comments). Not a live-path correctness bug (the functions are dead), but the WARN is genuine drift: Step C has been a silent no-op against a target that no longer exists since 2026-07-19, never cleaned up. Not fixed here (glue cleanup is not this pass's 2 assume-val-realisation scope; flagging for whoever next touches this glue file). Hazard 1 (hand-parsed RDF terms): CONFIRMED present via composition, same site as #454 — this module's own glue (bulk-load, lines ~669-684) calls Cottas_ondisk_runtime.parse_iri_token (defined in the separate cottas_ondisk_runtime.sh/cottas_runtime.sh glue) to eagerly parse predicate/graph tokens during companion bulk-load; per the brief, not fixed — third reachability path to the same #454 finding (module 1's COTTAS bulk path, module 3's dead OnDiskRuntime path, now this module's live bulk-load path).

Stale-anchor check on the module's own 6 core stub replacements (recurrence check 1): CLEAN — grep -c "^let (mmap_companion_open| read_companion_u32_le|read_companion_u64_le|read_companion_byte| read_companion_string|companion_file_size)" on the fresh .ml finds exactly 6, each once, zero leftover "Not yet implemented" text.

Tier from a fresh classifier run WITH --json, corrected after a misreading in an earlier draft of this entry (the classifier counts the local Lemma declaration itself, not just its erased extraction footprint): "assurance_tier": "local-lemmas-only" (moved from "merely-tot"), "local_refinement_lemma_count": 1, "assume_val_active": 7 — HONEST for what landed: one local correctness fact about the module's own arithmetic, zero W3C- refinement or algorithm-correctness theorems (the lemma doesn't relate two named shipping functions the way module 1/2's round-trip theorems do, so it doesn't cross into internal-refinement/algorithm- correctness). The tier move is real and modest — one honest rung up from merely-tot, not the two-rung jump modules 1 and 3's own audits describe for a round-trip- or corruption-rejection-shaped theorem. Gates (full rebuild, no .checked cache in this fresh worktree): RDF 1030 pass, 0 fail, 1 unsupported (of 1031); cli_literal_escape_roundtrip 5 pass, 0 fail (of 5); parquet_footer_version_gate_roundtrip 3 pass, 0 fail (of 3); cottas_ondisk_smoketest opens store_capabilities_sample.cottas cleanly, 5 quads; tests/unit/run-all.sh 20 pass, 28 fail (of 48) = baseline exactly, no deltas. Project assume-val total (active, per classifier): 125, unchanged (the lift is proof-only, does not retire an assume val — mmap_companion_close stays counted as active despite being unreachable, since the classifier counts declarations, not reachability).

#448 "Delete & dedupe" — the DELETE half: three dead modules removed (2026-08-17, branch delete-dead-store-modules): 🧹 the owner decision on issue #448, verbatim "Delete & dedupe", applied to the three modules wave 2 audits above found dead end-to-end, each re-verified dead again immediately before deletion (grep for the OCaml module name and the F* qualified name found zero hits outside each module's own files):

module assume val (before) verdict
Parser.BallyhooHDTQ.fst 13 dead, no caller anywhere
RDF.CottasStore.OnDiskRuntime.fst 15 dead, superseded 2026-07-06 (module 3 above)
Parser.BallyhooBloom.fst 0 dead, zero callers anywhere

Also removed: their ocaml-output/*.{ml,cmi} extracted artifacts, and the OnDiskRuntime-exclusive glue script experimental_ocaml_glue/cottas_ondisk_runtime_indexed.sh (confirmed exclusive — its sibling cottas_pagecache_indexed_runtime.sh targets RDF.CottasStore.PageCache, which has live callers besides OnDiskRuntime, so that glue script stays). RDF.Store.HDTTermCacheRegistry was explicitly NOT touched — it is live (called by the live Parser.BallyhooHDT.fst reader); an earlier sweep had wrongly flagged it dead, corrected by hand-check before this pass began.

Project assume-val total (active, per classifier): 125 -> 97 (-28, exactly the sum of the three modules' own declared assume vals above — no other module's count moved). merely-tot module count: -3 for this deletion specifically (all three deleted modules were classified merely-tot); the full inventory regeneration also shows one unrelated, pre-existing, independent change (RDF.CottasStore. OnDiskIndex moved merely-tot -> local-lemmas-only, a wave-2 module-4 lift already landed and described above, only now reflected because the committed assurance-inventory.json had not been regenerated since) — noted here so a reader diffing the committed JSON against an older copy does not misattribute that unrelated change to this deletion.

Rebuild: full ./build-ocaml.sh extract compile, clean, no errors. Gates: SPARQL 631 pass, 0 fail (of 631); RDF 1030 pass, 0 fail, 1 unsupported (of 1031); hdt-probe 75 pass, 0 fail (of 75); hdt_stage4_parity 6 pass, 0 fail (of 6) — proves the LIVE HDT path survived; cli_literal_escape_roundtrip 5 pass, 0 fail (of 5) — proves the LIVE COTTAS path survived; parquet_footer_version_gate_ roundtrip 3 pass, 0 fail (of 3); cottas_ondisk_smoketest opens store_capabilities_sample.cottas cleanly, 5 quads; tests/unit/ run-all.sh 20 pass, 28 fail (of 48) = baseline exactly, no deltas (the 28 pre-existing build failures are unrelated missing-module errors, e.g. RDFS_Closure_SemiNaive, not caused by this deletion).

Recovery path: the three modules' full source, their extracted .ml, and the deleted glue script are all recoverable from git history — see commit 0fa08a6f53b "Delete three dead store/parser modules (#448 delete half)" on branch delete-dead-store-modules.

8. Dep.Reachability — module-liveness deletion-safety (#448)#

Outside G1's RDF/SPARQL semantics scope (§ header above) — recorded here per the standing "no proof landing without a registry entry" rule, since it is a proved theorem with a real consumer (bin/depcheck, tools/module-liveness.py v3).

Stage Artifact Status Notes
Reachability closure spec Dep.Reachability.fst: reaches (inductive), closure_fuel/reachable (Tot, untrusted algorithm) ✅ PROVED reaches is the spec, independent of the algorithm; closure_fuel's fuel-adequacy argument is stated but NOT relied on for soundness.
Deletion-safety theorem closed_set_catches_all: is_closed edges acc /\ mem r acc /\ reaches edges r n ==> mem n acc ✅ PROVED Induction on the reaches derivation (decreases h); RRefl trivial, RStep uses FStar.List.Tot.for_all_mem to bridge is_closed's for_all to the per-edge fact.
Contrapositive corollary no_root_reaches: is_closed edges acc /\ all_mem roots acc /\ not (mem n acc) ==> forall r. memP r roots ==> ~(reaches edges r n) ✅ PROVED Built via FStar.Classical.impl_intro over a nested Lemma False helper (SMT closes the mem n acc / not (mem n acc) contradiction once both are literal ground facts in context).

Gates (2026-08-17, verified-liveness branch, fresh full rebuild — no .checked cache in this worktree before this landing): make verify-Dep.Reachability clean (no --lax, no --admit_smt_queries); ./build-ocaml.sh extract compile green with Dep.Reachability.fst in all three module lists (ALL_MODULES, COMMON_MODULES, js FSTAR_MODULES) and bin/depcheck built as part of the main native compile step (not an orphan binary — same $COMMON_MODULES link line as parquet_probe/ cottas_ondisk_smoketest); w3c_runner --rdf 1030 pass, 0 fail, 1 unsupported (of 1031) — exact match, no regression; tests/unit/ run-all.sh 21 pass, 28 fail (of 49) = the pre-existing 20 pass, 28 fail (of 48) baseline plus dep_reachability_unit (10 pass, 0 fail) newly passing.

Design: neither closure_fuel's fuel bound nor its closure implementation is trusted for soundness. bin/depcheck calls the extracted Dep_Reachability.reachable, then RE-CHECKS is_closed/all_mem — both DECIDABLE — on the actual output at runtime, and refuses (exit 2) if either fails; closed_set_catches_all is a fact about every set satisfying those two decidable premises, not about this one algorithm. Confirmed working by feeding depcheck a hand-doctored non-closed candidate set (a genuine graph's true closure with one downstream node removed): depcheck: REFUSED — reachable() output is not closed under edges ... Not trusting this result. (exit 2), and separately a closed-but-root-missing set: depcheck: REFUSED — reachable() output does not contain all roots. (exit 2). The anti-vacuity arm of tests/unit/dep_reachability_unit.ml pins the same property at the extracted-function level: is_closed accepts a diamond graph's true closure and REJECTS a hand-built proper subset of it.

Tool replacement (#448 Part 2): tools/module-liveness.py v3 drops the v2 ocamlobjinfo <unit>.cmx OCaml-layer oracle (required a full prior native+consumer build) for ocamldep -modules run directly over bin/*/*.ml and formal/fstar/ocaml-output/*.ml SOURCE, then shells out to bin/depcheck (built standalone from just Dep_Reachability.ml + depcheck.ml, not the full $COMMON_MODULES chain, if no build-produced binary is already present) as the reachability engine. strace -e openat -f python3 tools/module-liveness.py 2>&1 | grep -c '\.cmx' = 0.

v2-vs-v3 dead-set diff on the same fresh-rebuilt tree: v2 saw 200 project units (only modules with a committed .cmx) and reported 22 DEAD; v3 saw 221 project units (every extracted .ml, .cmx or not) and reported 35 DEAD. The two universes are not the same set, so raw counts do not compare directly — split by what each tool could see:

Project assume-val total: unchanged (this landing adds proof-only F*

9. Unified model theory (#598)#

Stage 1 (landed 2026-08-25): RDF core semantics + datasets embedded into the CL/IKL theory layer (formal/lean4/L4Factoidal/Unified/), proved adequate in BOTH directions against the native Lean formalization. Design: docs/designissues/2026-08-25-unified-semantics-lean.md (stage 1 correction notes there record the deviations: colon-free bound-name spelling, tagged lift domain, unscoped named-graph bodies). All rows below are Lean 4 (L4Factoidal.Unified), no sorry / user axiom / partial / native_decide; axiom audit on every gate theorem: propext, Classical.choice, Quot.sound only.

Theorem Module Native anchor Status Fragment / hypotheses
unified_adequate_simple — Entails [rdfToTheory g] (rdfToTheory h) ↔ RDF.SimpleEntailsMt g h Unified/RdfAdequacy.lean RDF.SimpleEntailsMt (RDF/Semantics.lean) ✅ PROVED (2026-08-25) — full iff NONE — no triple-term-freedom hypothesis (both sides read RDF 1.2 triple terms as the same uninterpreted function)
unified_adequate_simple_decided — ... ↔ RDF.simpleEntails g h = true Unified/RdfAdequacy.lean RDF.simpleEntails_iff_mt + RDF.SimpleRefinement.simpleEntails_iff_spec ✅ PROVED (2026-08-25) GraphTtFree g, GraphTtFree h (where the native Herbrand construction applies; hypotheses shown non-degenerately satisfiable in Unified/Witnesses.lean)
rdfToTheory_merge — merge is conjunction: EntailEquiv [rdfToTheory (mergeGraphs g h)] [rdfToTheory g, rdfToTheory h] Unified/RdfAdequacy.lean Graph.prefixBnodes renaming (RDF/Graph.lean, RDF/DatasetMerge.lean), RDF.tripleHolds_agree ✅ PROVED (2026-08-25) — satisfaction-equivalence (stronger than mutual entailment) —
rdfToTheory_union_entails_left / _right / _merge — shared-scope union entails each part and the merge Unified/RdfAdequacy.lean same ✅ PROVED (2026-08-25) —
union_shared_scope_strict — the converse REFUTED: separate closures do not entail the shared-label union (witness pair, decision-procedure separation simpleEntails_merge_union_false by decide) Unified/RdfAdequacy.lean RDF.simpleEntails ✅ PROVED (2026-08-25) — strictness witness concrete witness pair unionG/unionH
satisfies_rdfToTheory_restrict / satisfies_rdfToTheory_lift — the transport pair's satisfaction transfer, both directions, both interpretations Unified/RdfTransport.lean RDF.Satisfies (RDF/Semantics.lean) ✅ PROVED (2026-08-25) — full iff each —
bnodeName_ne_iri — bound-name freshness against EVERY well-formed IRI string, unconditional Unified/RdfEmbed.lean RDF.isIri ✅ PROVED (2026-08-25) — (colon-free spelling makes the design doc's freshness obligation hypothesis-free)
datasetToTheory_asserts_default — the dataset sentence entails its default graph's translation Unified/DatasetEmbed.lean RDF.Dataset (RDF/Graph.lean) ✅ PROVED (2026-08-25) —
datasetToTheory_no_named — a named-graph-free dataset translates to exactly its default graph's sentence Unified/DatasetEmbed.lean — ✅ PROVED (2026-08-25) — definitional equation —
dataset_decoration_asserts_nothing — a named-graph-only dataset does NOT entail its named graph's content (RDF 1.1 Concepts §4: datasets carry no entailment semantics). RESTATED SCOPE 2026-08-26: after the #609 item-3 repair this is the statement about a named graph the default graph does NOT decorate with urn:cl:def:asserts — decorationDataset has an empty default graph. An ASSERTED named graph's content IS entailed (embedding_entails_content) Unified/Witnesses.lean separating model namesOnlyInterp ✅ PROVED (2026-08-25, scope restated 2026-08-26) — refutation witness concrete decorationDataset
graphAsserted + assertedGraphAtom + sat_assertedGraphAtom — THE #609 ITEM-3 REPAIR: a named graph the DEFAULT graph decorates with urn:cl:def:asserts contributes the conjunct atom (that (rdfBody G)) [] — CLIF's cancelling-parentheses assertion, and the position CL.IklRespectsThat constrains. sat_assertedGraphAtom is CL.sat_assert_that at that conjunct, so under IKL coherence it forces the graph's content exactly. (graphAsserted_eq_assertsDecorated, which pinned this test to a second spelling in the deleted CL/IklRegime.lean, is REMOVED with #626 — there is now one spelling.) Unified/DatasetEmbed.lean, Unified/RdfEmbed.lean CL.sat_assert_that (CL/Semantics.lean) ✅ PROVED (2026-08-26) none
decorationOnlyToTheory + datasetToTheory_entails_decorationOnly — the SUPERSEDED decoration-only reading, retained so the divergence theorems have a subject; the repaired reading entails it. The inclusion is PROPER — decorationOnly_strictly_weaker Unified/DatasetEmbed.lean — ✅ PROVED (2026-08-26) — refinement —
rdfToTheory_satisfiable / datasetToTheory_satisfiable, unified_entails_not_everything, unified_entails_instance, propAlphaInvariant_satisfiable / _entails_not_everything / alphaKeyed_distinguishes Unified/Witnesses.lean RDF/SemanticsHypothesisWitness.lean discipline ✅ PROVED (2026-08-25) non-vacuity guards: every stage 1 relation shown neither empty nor the everything-relation

D-entailment landing (2026-08-25, Unified/DSchema.lean): the datatype-map schema dSchema D (value identification via RDF.literalValueEq D + ill-typed exclusion via RDF.literalIllFormed D, design doc §2.5/§5.1) and the native model-theoretic anchor RDF.DInterpCond / RDF.DEntailsMt (EntailsUnder over the D-interpretations), introduced in the same module because the native tree had none — RDF/EntailmentTheorems.lean deliberately gives the literalValueEq regime variants no soundness theorem. REPAIRED same day per #602: the exclusion clause and schema now cover triple-term-INTERIOR ill-typed literals (RDF.termIllTypedMention), mirroring RDF 1.2 Semantics WD (7 April 2026) §5 I(E) = IT(I(E.s), I(E.p), I(E.o)) composed with §7.1 "cannot denote anything … any triple containing the literal must be false" and the W3C rdf12 malformed-literal test; the pre-repair top-level-only bundle survives as DInterpCondTopLevel with a strictness separation. Axiom audit on every row below: propext, Classical.choice, Quot.sound only.

Theorem Module Native anchor Status Fragment / hypotheses
unified_adequate_d — EntailsSchema condTrue (dSchema D) [rdfToTheory g] (rdfToTheory h) ↔ RDF.DEntailsMt D g h Unified/DSchema.lean RDF.DEntailsMt (introduced there; design doc §4.1's parenthetical) ✅ PROVED (2026-08-25) — full iff NONE — no side condition, any D, any graphs
unified_d_illtyped_entails_all / dEntailsMt_illtyped / RDF.dEntailsMt_illtyped_native — a premise whose object MENTIONS an ill-typed recognised literal (top-level, or triple-term interior at any depth) entails everything (RDF 1.1 Semantics §7.2; RDF 1.2 Semantics WD §5 + §7.1) Unified/DSchema.lean RDF.termIllTypedMention; verdict agreement with RDF.Regime.inconsistent pinned by #guard ✅ PROVED (2026-08-25, repaired form per #602) any mentioned occurrence — the interior case is the malformed-literal shape
dValueSchema_alone_insufficient + dSchema_exclusion_does_work — the §5.1 separating model: dSepInterp satisfies the whole value-identification half plus the translated ill-typed graph and refutes the exclusion axiom, so value identification alone does not give the §7.2 everything-verdict and the exclusion rows do Unified/DSchema.lean separating model dSepInterp ✅ PROVED (2026-08-25) — exclusion-schema non-redundancy concrete witness dBadGraph ("yes"^^xsd:boolean)
dEntailsMt_value_instance / unified_d_value_instance / dNum_not_simple — "1"^^xsd:integer D-entails "01"^^xsd:integer in the same triple, and simple entailment refutes the pair: D strictly extends simple Unified/DSchema.lean RDF.literalValueEq, RDF.simpleEntails_iff_mt ✅ PROVED (2026-08-25) concrete witness pair dNumG/dNumH
noRel_satisfiesSchema_d, dSchema_entails_not_everything, noExt_dCond, dEntailsMt_not_everything Unified/DSchema.lean RDF/SemanticsHypothesisWitness.lean discipline ✅ PROVED (2026-08-25) non-vacuity guards: schema and condition bundle satisfiable for every D; neither relation is the everything-relation
RDF.regimeEntails_d_sound_mt — SOUNDNESS of the executable D-regime: regimeEntails .d D g h = true → RDF.DEntailsMt D g h, with no side condition (the #602 repair makes the interior-collecting inconsistency shortcut sound; termMatch_valueEq_denot + denot_instance_term make value-equal matching sound, triple-term interiors included) Unified/DSchema.lean RDF.regimeEntails .d ✅ PROVED (2026-08-25) NONE — unconditional
unified_adequate_d_decided_sound — the decided corollary, SOUND HALF: executable true implies schema entailment (composition of the row above with unified_adequate_d) Unified/DSchema.lean RDF.regimeEntails .d ✅ PROVED (2026-08-25) NONE — unconditional
dEntailsMt_tt_illtyped / wdMalformed_dEntailsMt — the FLIPPED PIN of #602: a used triple term with an ill-typed interior literal makes the premise D-inconsistent (the W3C rdf12 malformed-literal expectation); executable agreement pinned by #guard. The failing-pin run against the pre-repair semantics is recorded on the issue (the superseded dEntailsMt_tt_gap proved its negation and is REMOVED — its content survives as topLevel_exclusion_insufficient_for_tt, stated about the superseded bundle) Unified/DSchema.lean RDF.dEntailsMt_illtyped_native, witness dTtGraph / wdMalformedGraph ✅ PROVED (2026-08-25) concrete witnesses
DInterpCondTopLevel + dInterpCond_topLevel / ttSep_not_dCond / topLevel_exclusion_insufficient_for_tt — the pre-repair top-level-only bundle is a strict weakening: ttSepInterp meets it while satisfying dTtGraph, so the interior clause is the exact ingredient the repair added Unified/DSchema.lean separating model ttSepInterp ✅ PROVED (2026-08-25) — clause non-redundancy concrete witness dTtGraph
DECIDED COROLLARY, COMPLETE HALF — NOT CLAIMED: RDF.DEntailsMt D g h → regimeEntails .d D g h = true. Named open lemma; NOT a triple-term matter after the repair. Needs (a) a D-Herbrand interpretation quotienting literals by literalValueEq D (clause 1 forces value-equal literals to one denotation, so the plain term model does not qualify), and (b) completeness of searchInstance under the regime's restricted Regime.bindable (entailsWith_complete requires bindable universally true) Unified/DSchema.lean RDF.regimeEntails .d ⬜ OPEN — named lemma pair (a)+(b) expected hypotheses once attempted: none beyond D ⊆ modelledDatatypes; GraphTtFree is NOT expected to be needed (the D-Herbrand iTt can be injective, unlike the simple Herbrand constant)

NOT claimed at stage 1: any RDFS/OWL/SPARQL row (stages 2+ — rdfSchema's rdfD2 typing rows and the rdfs:range clash rule of RDF/EntailmentRdfsDatatypeClash.lean ride with stage 2, not with dSchema), and the N-Quads round-trip corollary (blocked on the general parser round-trip theorem, #576 — the native theorem exists only for the empty graph).

Stage 2 (landed 2026-08-25, recovered from an interrupted agent run, verified and completed): the ρdf / x-rdfscore schema (Unified/RhoDfSchema.lean) and the full RDFS + RDFS+D schemas with the rdfs:range clash family and the type-application bridge (Unified/RdfsSchema.lean). Design-doc correction notes 8–10 record the deviations (unconditional ρdf gate with hypotheses moved to the decided corollary; full-iff RDFS adequacy where the design predicted soundness-only; the bridge as a separate conservative schema; the rdf:XMLLiteral ∈ D weakening on closure soundness). Axiom audit on every row below: propext, Classical.choice, Quot.sound only (in-source #print axioms).

Theorem Module Native anchor Status Fragment / hypotheses
satForall_plains + satisfies_hornRow_iff + liftInterp_sat_hornRow — the Horn-row combinator: ONE satisfaction lemma characterising a universally quantified definite Horn row's CL satisfaction as its native reading over restrictInterp, plus the generic lift transport Unified/RhoDfSchema.lean CL.SatForall (CL/Semantics.lean), restrictInterp/liftInterp ✅ PROVED (2026-08-25) — full iff (the combinator every schema row goes through) row variables colon-free, row atoms scoped (decidable side conditions, discharged by decide per row)
satisfies_rowRdfs{2,3,5,7,9,11}_iff, satisfiesSchema_rhoDf_iff — the six ρdf rows are exactly the native condition bundle RDF.RhoDfConditions through the restriction Unified/RhoDfSchema.lean OWL.CondDomain/CondRange, RDF.CondSubPropertyOf*/CondSubClassOf* ✅ PROVED (2026-08-25) — full iff each —
unified_adequate_rhoDf — EntailsSchema condTrue rhoDfSchema [rdfToTheory g] (rdfToTheory h) ↔ RDF.RhoDfEntails g h Unified/RhoDfSchema.lean RDF.RhoDfEntails (RDFS/RhoDfCompleteness.lean) ✅ PROVED (2026-08-25) — full iff, UNCONDITIONAL (stronger than the design §4.2 statement; correction note 8) NONE
RDF.rhoDf_derives_holds — everything RDFS.Derives derives is true in every ρdf interpretation (interpretation-level rule soundness, introduced here); RDF.rhoDfEntails_closure_iff — ρdf entailment invariant under replacing the premise by its executable closure Unified/RhoDfSchema.lean (RDF namespace) RDFS.closure_sound / closure_extensive (RDFS/ClosureTheorems.lean) ✅ PROVED (2026-08-25) —
RDF.rhoDfClosed_of_check / RDF.rhoDfModelFrag_of_check — executable sufficient checks (rhoDfClosedCheck incl. the rdfs9 blank-node-class conclusions the engine step does not emit; RDFS.isRhoDfFrag) for the decided corollary's hypotheses Unified/RhoDfSchema.lean (RDF namespace) RDF.RhoDfClosed, RDF.RhoDfModelFragGraph ✅ PROVED (2026-08-25) — sufficiency direction only —
unified_adequate_rhoDf_decided — the decided corollary: schema entailment ↔ RDF.simpleEntails (RDFS.closure g fuel) h = true Unified/RhoDfSchema.lean RDF.rhoDfClosed_iff (Herbrand), simpleEntails_iff_spec ✅ PROVED (2026-08-25) RhoDfClosed (closure g fuel), RhoDfModelFragGraph (closure g fuel), GraphTtFree h — each decide-dischargeable via the checks; positive instance unified_rhoDf_demo discharges all three
rhoDf_not_entails_selfLoop_unified / rhoDfEntails_not_selfLoop + rhoDfSchema_satisfiable — Finding C-1's negative half at the unified level, plus non-vacuity Unified/RhoDfSchema.lean C-1 witness pair (RDFS/RhoDfCompleteness.lean) ✅ PROVED (2026-08-25) — refutation + witness concrete pair c1Prem/c1Concl; also the not-everything guard for rhoDfSchema
unified_adequate_rdf — EntailsSchema condTrue rdfSchema … ↔ RDF.RdfEntails g h (RDF rung: rowRdfProperty + the RDF axiom family incl. the infinite rdf:_n rows via RDF.RdfAxiomatic) Unified/RdfsSchema.lean RDF.RdfEntails (RDF/EntailmentRdfsModelTheory.lean) ✅ PROVED (2026-08-25) — full iff, unconditional rdfSchema carries no D parameter (correction note 9b)
unified_adequate_rdfs — EntailsSchema condTrue (rdfsSchema Dset) … ↔ RDF.RdfsEntails Dset g h (17 Horn rows + both axiom families + rdfs1 rows for Dset and dMinimal) Unified/RdfsSchema.lean RDF.RdfsEntails ✅ PROVED (2026-08-25) — full iff, unconditional, every Dset (stronger than the design's soundness-only; correction note 9a) NO decided corollary (Finding C-1 blocks the executable characterisation)
RDF.derivesFull_holds — truth preservation over RDFS.DerivesFull (one case per §8/§9 rule row); RDF.axiomaticTriples_hold — the engine's seeded axiom set holds under the conditions Unified/RdfsSchema.lean (RDF namespace) RDFS.fullClosure_sound (RDFS/FullClosureTheorems.lean) ✅ PROVED (2026-08-25) axiomaticTriples_hold: harvested rdf:_n slice genuine (IsRdfMemberIri) AND rdf:XMLLiteral ∈ D (correction note 10b; #guards pin the seed-table/spec-table mismatch)
unified_rdfs_closure_sound — every triple of RDFS.fullClosure D cmps g is schema-entailed by g's translation Unified/RdfsSchema.lean RDFS.fullClosure ✅ PROVED (2026-08-25) — soundness only (completeness not claimed, C-1) ∀ c ∈ cmps, IsRdfMemberIri c; rdf:XMLLiteral ∈ D (recorded weakening, correction note 10b)
rdfs_entails_selfLoop / rdfs_entails_selfLoop_unified — C-1's positive half: the full RDFS schema entails the subClassOf self-loop the ρdf schema refutes → rdfsSchema STRICTLY stronger than rhoDfSchema Unified/RdfsSchema.lean IC condition + subClassOf reflexivity ✅ PROVED (2026-08-25) shared witness pair with the ρdf refutation
rdfSchema_satisfiable / rdfsSchema_satisfiable / rdfSchema_entails_not_everything / rdfsSchema_entails_not_everything Unified/RdfsSchema.lean trivialInterp / separatingInterp (RDF/SemanticsHypothesisWitness.lean) through the lift ✅ PROVED (2026-08-25) non-vacuity guards, every Dset
RDF.DRangeCond / RDF.RdfsDInterpCond / RDF.RdfsDEntailsMt — the combined RDFS+D condition bundle and its model-theoretic entailment, introduced natively here (the native tree had only the executable rdfsDInconsistent) Unified/RdfsSchema.lean (RDF namespace) RDF/EntailmentRdfsDatatypeClash.lean (executable side) ✅ DEFINED (2026-08-25) fragment of §7+§9 the executable machinery expresses; full §7 value-space completeness not claimed
rangeClashSchema + satisfies_rangeClashAx_iff + lift/restrict transfers — the rdfs:range datatype clash as a negated-existential axiom family (the dExclusionSchema pattern) Unified/RdfsSchema.lean RDF.DRangeCond ✅ PROVED (2026-08-25) — full iff characterisation one row per recognised c ∈ D and differently-typed literal
unified_adequate_rdfs_d — EntailsSchema condTrue (rdfsDSchema Dset D) … ↔ RDF.RdfsDEntailsMt Dset D g h Unified/RdfsSchema.lean RDF.RdfsDEntailsMt ✅ PROVED (2026-08-25) — full iff, unconditional, every Dset/D —
rdfsDSchema_clash_unsat / unified_rdfsD_clash_entails_all / rdfsDEntailsMt_clash / rangeClash_demo — a range/datatype-clash premise entails everything; concrete demo agrees with the executable detector (#guard RDF.rdfsDInconsistent …) Unified/RdfsSchema.lean RDF.rdfsDInconsistent rule (b) ✅ PROVED (2026-08-25) clash pattern hypotheses (decl + data triple shape); demo rangeClashDemoG
rdfsDSchema_satisfiable_nil / rdfsDEntailsMt_not_everything_nil / rdfsDSchema_entails_not_everything_nil — non-vacuity of the combined bundle and schema Unified/RdfsSchema.lean trivialInterp / separatingInterp ✅ PROVED (2026-08-25) stated at D = [] (datatype clauses vacuous there); see the open-item row below for nonempty D
typeBridge + satisfies_typeBridge_iff + bridgeify + restrictInterp_bridgeify + typeBridge_conservative — the LBase type-application bridge (forall (x c) (iff (rdf:type x c) (c x))) as a SEPARATE schema, conservative over translated graphs (rel-surgery: unary predication redefined from the binary rdf:type extension, invisible to restrictInterp) Unified/RdfsSchema.lean LBase §2; rdfsSchema gate iff ✅ PROVED (2026-08-25) — conservativity as full iff bridge NOT foldable into rdfsSchema (correction note 9c)
bridge_derives_classApp / rdfsSchema_no_classApp — the bridge is NOT conservative outside the translated fragment, pinned on the class-application sentence (C a) Unified/RdfsSchema.lean liftInterp's empty non-binary extensions ✅ PROVED (2026-08-25) — separation pair concrete bridgeDemoG
rdfsCoreSchema (= rhoDfSchema, definitional) — the x-rdfscore regime's schema, named for the stage 6 regime table Unified/RhoDfSchema.lean RDFS/RegimeDispatch.lean ✅ DEFINED (2026-08-25) — rdfsCoreSchema_eq : rdfsCoreSchema = rhoDfSchema —
FINITE-SLICE-SUFFICES (§5.7) NOT PROVED — consequences mentioning only harvested rdf:_n IRIs derivable from the harvested instances. NO landed theorem consumes it: the schemas index rows by the native predicates carrying the full infinite families, so both sides of every gate iff quantify over the same family. Load-bearing only for a decided full-RDFS corollary, which Finding C-1 independently blocks — RDF 1.1 Semantics Appendix A finite-enumeration recipe ⬜ OPEN (recorded gap, correction note 10a) —
RDFS+D SATISFIABILITY FOR NONEMPTY D NOT PROVED — a model of the full §9 bundle plus the D and range-clash clauses with recognised datatypes needs a term-model construction (CondResource reflexivity against literal-excluding conditions defeats finite ad-hoc models) — — ⬜ OPEN (recorded gap; witnesses stated at D = []) —

Stage 3 (landed 2026-08-25): Datalog as the named computable-fragment class of the unified theory — Unified/Datalog.lean (the class, its Herbrand semantics, and the TWO GENERIC least-fixpoint theorems, proved once for every program) and Unified/DatalogClosures.lean (the closure engines exhibited as programs of the class, with the class boundary recorded in that module's header). Design-doc correction notes 12–15 record the deviations (term-valued predicate position; well-formedness as a proof field, so existential heads are unwritable; membership-equality exhibit statement with the stage 2 decided-corollary hypotheses; RIF Core deferred to its own stage — landed 2026-08-26, correction notes 38-41). Axiom audit on every row below: propext, Classical.choice, Quot.sound only (in-source #print axioms).

Theorem Module Native anchor Status Fragment / hypotheses
DTerm/DAtom/DRule/DatalogProgram — definite Horn over n-ary predications, predicate position a term (variable predicates legal; CL is unsegregated), NO function symbols, NO existential heads and NO falsity heads BY CONSTRUCTION (DatalogProgram.wf is a proof field: definiteness + the colon-free variable discipline) Unified/Datalog.lean design doc §3 stage 3, §5.6 ✅ DEFINED (2026-08-25) rdfD1Shape_not_wf (Unified/DatalogClosures.lean) pins that a witness-minting rule shape fails the gate
sat_datom + satisfies_ruleSentence_iff — the n-ary generalisation of the stage 2 hornRow machinery: ONE satisfaction lemma characterising a rule sentence's CL satisfaction as its native reading at every valuation (DAtom.Holds, stated directly over CL.Interp — no arity-2 restriction) Unified/Datalog.lean satForall_plains (Unified/RhoDfSchema.lean), CL.Sat ✅ PROVED (2026-08-25) — full iff rule wf (colon discipline), carried by the program structure
matchBody_sound / matchBody_complete + DRule.mem_conclusions_of_instance — the executable substitution search is sound (every found binding instantiates the body into the fact set) and complete (every grounding substitution is found; definiteness turns body coverage into head coverage) Unified/Datalog.lean — (new executable layer) ✅ PROVED (2026-08-25) completeness needs definiteB — exactly what the class gate enforces
DatalogProgram.lfp_sound / derives_mem_lfp — the fuel-bounded least fixpoint against the rule relation DatalogProgram.Derives, both directions (the second under FuelAdequate, executable check saturatedCheck, decide-dischargeable — the rhoDfClosedCheck pattern) Unified/Datalog.lean RDFS/FixedPoint.lean shape ✅ PROVED (2026-08-25) —
datalog_lfp_sound — GENERIC theorem 1: every atom of any program's least fixpoint is entailed by the program-as-schema plus the facts over every CL interpretation (step-shaped induction, the rhoDf_derives_holds shape, done once for the class) Unified/Datalog.lean EntailsSchema (Unified/Theory.lean) ✅ PROVED (2026-08-25) NONE (any fuel, any facts)
datalog_lfp_complete + herbInterp / herb_satisfiesSchema — GENERIC theorem 2: every GROUND atom entailed by schema + ground facts is in the fuel-adequate fixpoint; the Herbrand interpretation of the fixpoint itself is the minimal model (the RhoDfCompleteness construction done once at the generic level) Unified/Datalog.lean Herbrand pattern of RDFS/RhoDfCompleteness.lean ✅ PROVED (2026-08-25) facts ground, atom ground, FuelAdequate — completeness claimed for GROUND-ATOMIC consequences only (design doc §5.6; that is the class's claim level, stated)
datalog_lfp_iff_entails — the stage 3 gate theorem (§4.3): fixpoint membership ↔ EntailsSchema condTrue p.toSchema for every well-formed program Unified/Datalog.lean both rows above ✅ PROVED (2026-08-25) — full iff union of the two rows' hypotheses
toSchema_satisfiable + demo_path_entailed / demo_tri_entailed / demo_not_entailed_reverse — non-vacuity: every program's schema is satisfiable; a transitive-closure demo (with an arity-3 rule pinning the n-ary machinery) has a positive, a ternary and a REFUTED instance, all end to end through the executable fixpoint Unified/Datalog.lean SemanticsHypothesisWitness discipline ✅ PROVED (2026-08-25) concrete demoProgram/demoFacts
rhoDfProgram + derives_to_datalog / datalog_derives_mem_closure — the ρdf closure as a Datalog program; the specification relation RDFS.Derives maps INTO the program's derivations (unconditional), and every program derivation decodes into a ρdf-CLOSED engine closure (via the DIAGONAL relations Rdfs*Derives, blank-node classes included) Unified/DatalogClosures.lean RDFS/RdfsCore.lean, RDF/EntailmentRdfsSpec.lean, RDF.RhoDfClosed ✅ PROVED (2026-08-25) decode: closure ρdf-closed + model fragment (the stage 2 decided-corollary hypothesis pair)
rhoDf_closure_datalog_agree + rhoDf_lfp_image — ρdf ENGINE / Datalog agreement, GENERAL graphs, as MEMBERSHIP equality; the fixpoint holds no atoms outside the encoding's image Unified/DatalogClosures.lean RDFS.closure, RDFS.closure_sound / closure_extensive ✅ PROVED (2026-08-25) — membership iff rhoDfClosedCheck (closure g m), isRhoDfFrag (closure g m), saturatedCheck fuel adequacy, RhoDfModelObjectOk t.o — all but the last decide-dischargeable; instance rhoDf_demo_agree discharges all
rhoDf_engine_iff_datalog_entails — the design doc §8.2 claim at its stated level: membership in the ρdf ENGINE's closure ↔ CL entailment of the triple's atom-sentence from the program-as-schema (ground-atomic consequences, ρdf model fragment) Unified/DatalogClosures.lean composition of the agreement row and the gate theorem ✅ PROVED (2026-08-25) same hypothesis set; decided instance rhoDf_demo_entails
rdfsPlusProgram (six ρdf rows + eq-sym/eq-trans/eq-rep-s/o/p, prp-symp, prp-trp, prp-inv1/2, prp-fp, prp-ifp, cax-eqc1/2, prp-eqp1/2 + the four schema-level inverseOf domain/range flips — the RL closure's non-list, non-clash core) + rdfsPlus_demo_trp_agree + rdfsPlus_demo_entails + rdfsPlusDemoAgrees #guards Unified/DatalogClosures.lean RDFS.rdfsPlusClosure (RDFS/RDFSPlus.lean), OWL/RLClosure.lean row functions ✅ PROVED (2026-08-25) — DEMO-INSTANCE membership equality, both directions: a decided theorem on the TransitiveProperty demo; native-evaluated build-time #guard pins on the sameAs and inverseOf demos (their substitution closures exceed the kernel decide budget); engine saturation at the used fuel pinned by #guard GENERAL bridging NOT claimed for this tier (correction note 14): the native tier claims no chain-level completeness, and the engine's subjIri guards restrict firings the program does not
DATALOG CLASS BOUNDARY — outside the class, each with its reason: rdfD1 / rdfs1-2004 / lg-gl (existential heads — unwritable by construction); the 13 RL clash rows (falsity heads — a DRule head is an atom; their schema-level home is rangeClashSchema); the RL list-valued rows prp-spo2, prp-key, cls-int1/2, cls-uni, cls-oo, cax-adc, scm-int, scm-uni (unbounded collection premises — infinite rule families, stage 4 material as sentence families); the infinite rdf:_n axiomatic fact families (finite slices only). rdfs6/rdfs10/eq-ref are IN the class but engine-excluded, so the exhibits exclude them for agreement Unified/DatalogClosures.lean module header design doc §5.6 ✅ RECORDED (2026-08-25) —
GENERAL RDFS-PLUS / RL-CORE BRIDGING NOT PROVED — a ρdf-style general membership iff for rdfsPlusProgram needs per-row diagonal specification relations + a closedness predicate for the tier (none exist natively) and a fragment predicate reconciling the engine's IRI-subject guards — — ⬜ OPEN (recorded gap, correction note 14) —
rifCoreToTheory + rifDRules / rifCoreProgram? / rifCoreProgram_of_fragment — a RIF Core rule set as universally closed implications, through the frame / member / subclass desugaring of the RIF/RDF/OWL combination specification §5; a positional atom becomes an n-ary predication at ANY arity (arity 2 IS the triple predication). Every rule set passing rifCoreFragmentB IS a program of the class, DRule.wfB discharged as the proof field Unified/RifEmbed.lean RIF/Translation.lean desugaring, RIF/Syntax.lean ✅ DEFINED + PROVED (2026-08-26) rifCoreFragmentB (see the FRAGMENT row below)
rifTripleFact_sentence / rifGraphFacts_sentences / satisfiesAll_tripleAtoms_iff / satisfies_rdfToTheory_single — the Datalog sentences ARE Unified/RdfEmbed.lean's own triple predications on the IRI fragment (no tagged vocabulary of their own, unlike Unified/DatalogClosures.lean's "i:"/"b:" encoding), and the premise list g.map tripleAtom is satisfaction-equivalent to [rdfToTheorySk g] Unified/RifEmbed.lean tripleAtom, rdfToTheorySk, sat_rdfBody ✅ PROVED (2026-08-26) — equalities and full iffs TripleIriOnly / GraphIriOnly
rifCore_lfp_iff_entails_atom — the RIF Core gate theorem, n-ary form: a ground atom is in the rule set's Datalog least fixpoint over a graph's facts ↔ EntailsSchema condTrue (fun s => s ∈ rifCoreToTheory rs) [rdfToTheorySk g] a.sentence. Obtained by instantiating datalog_lfp_iff_entails, not re-proved Unified/RifEmbed.lean DatalogProgram.lfp, generic theorems 1 and 2 ✅ PROVED (2026-08-26) — FULL IFF p.rules = rifDRules rs, GraphIriOnly g, atom ground, FuelAdequate (saturatedCheck, decide-dischargeable). NONE of the four mentions the conclusion
rifCore_lfp_iff_entails — §4.7's triple shape: rifTripleFact t ∈ p.lfp (rifGraphFacts g) fuel ↔ EntailsSchema condTrue (fun s => s ∈ rifCoreToTheory rs) [rdfToTheorySk g] (rdfToTheory [t]). The LEFT-HAND SIDE IS THE DATALOG LEAST FIXPOINT, NOT RIF.Engine.saturate — see the OPEN row below Unified/RifEmbed.lean RIF/Saturate.lean reading of a graph ✅ PROVED (2026-08-26) — FULL IFF as the row above, plus TripleIriOnly t
rifCoreToTheory_satisfiable + rifCore_demo_member_entails / rifCore_demo_frame_entails / rifCore_demo_nary_entails / rifCore_demo_not_entailed / rifCore_demo_satisfiable — non-vacuity: EVERY rule set's schema is satisfiable, and on a three-rule demo (membership, frame, ternary positional head) a membership, a frame and a TERNARY consequence are entailed while ex:b # ex:D is REFUTED through the completeness half. All decided end to end Unified/RifEmbed.lean toSchema_satisfiable, SemanticsHypothesisWitness discipline ✅ PROVED (2026-08-26) concrete demoRifRules / demoRifGraph
RIF FRAGMENT BOUNDARY — outside the class, each with its reason and each pinned by a decided rejection theorem: Equal and External(pred:…) body atoms (no equality atom, no built-in predicate in the class — all 197 RIF-DTB built-ins are therefore outside); Or and Exists bodies; List / External(func:…) / uninterpreted function terms (the class has NO function symbols); constants outside rif:iri space or with a non-IRI lexical form; colon-carrying variable names; unbound head variables (rifExistentialHeadRule_rejected). DATA side: blank-node and literal terms, because bnodeName is colon-free by construction and embedTerm maps a literal to a FUNCTIONAL term Unified/RifEmbed.lean module header design doc §5.6, Unified/RdfEmbed.lean ✅ RECORDED + PINNED (2026-08-26) —
§4.7's unified_adequate_rifCore AGAINST THE NATIVE ENGINE NOT PROVED — RIF/Engine.lean's groundTm, matchFormula and qualifyTm are partial def: opaque to the kernel, no equation lemmas, so nothing about what matchFormula COMPUTES is available to a proof and decide cannot evaluate it either. RIF/EngineTheorems.lean is written around the same limit (its Licensed predicate mentions matchFormula's output rather than characterising it, and it declines to call itself soundness). Agreement between RIF.Saturate.saturateGraph and the Datalog fixpoint is pinned by rifEngineDatalogAgrees #guards under COMPILED evaluation — evidence at the strength of the RDFS-Plus demo pins, not a theorem. Prerequisite: make the three definitions total Unified/RifEmbed.lean #guards only RIF.Saturate.saturateGraph, RIF.closure ⬜ OPEN (recorded gap, correction note 38) —
RIF ENGINE / DESUGARING DIVERGENCE — RIF/Saturate.lean reads a triple as a frame, and additionally as membership/subclass for rdf:type/rdfs:subClassOf, but does NOT re-enter a DERIVED membership as a frame. The desugaring here collapses both readings onto one triple predication, so a rule with a ?x[rdf:type -> ?c] body fires on a derived membership in the Datalog reading and not in the engine. Programs of that shape are outside what the #guards pin; the theorems never mention the engine Unified/RifEmbed.lean module header RIF/Saturate.lean factsOfTriple / tripleOfGAtom ✅ RECORDED (2026-08-26) —

Stage 4 (landed 2026-08-26): OWL 2 RL under the unified theory — OWL/RLSemantics.lean (per-row RlCond* conditions + the soundness induction over OWL.RL.Derives), OWL/RLHerbrand.lean (the enriched Herbrand completeness model), Unified/OwlRlSchema.lean (the rule table as an axiom schema, built row-family-wise) and Unified/OwlRlAdequacy.lean (the gate theorems). Design-doc correction notes 16-20 record the deviations. Axiom audit on every row below: propext, Classical.choice, Quot.sound only (in-source #print axioms).

Row-family counts (re-measured 2026-08-26 after the cardinality-row and list-valued-row landings): 67 plain Horn rows as DRules indexed by RlRowId; 5 guarded / table-indexed DRule families (eq-ref predicate conclusion at a non-reserved IRI, cax-adc-dw at a distinct IRI pair, dt-type1 over builtinDatatypeAxioms, dt-rng-intersect over rangeIntersectLicenses, xsd-axioms over datatype-position predicate x XSD IRI x xsdAxiomTriples); 2 per-length DRule families (prp-spo2, prp-key, one rule per collection length); 12 plain clash rows plus 1 clash family (cax-adc) as falsity-headed DNeg sentences; 3 rows in the interpretation-class condition OwlRlInterpCond. Total 91 rows: 88 as object-language sentences, 3 as interpretation conditions. (Before 2026-08-26: 66 Horn / 0 per-length / 9 clash / 9 bundled, i.e. 82 and 9.)

Theorem Module Native anchor Status Fragment / hypotheses
RlCond* (79 rows) + RlNCond* (13 rows) + the bundles RlConditions / RlClashConditions — one model-theoretic condition per OWL.RL.Derives constructor and per Clash constructor, in the OWL 2 RL/RDF table-row citation discipline OWL/RLSemantics.lean OWL/RLRules.lean constructors; OWL 2 RDF-Based Semantics ✅ DEFINED (2026-08-25) reserved urn:cl:def:listMember / urn:cl:def:typedAllMembers helper predicates give the ListMember-shaped rows a Horn form
rl_derives_holds — truth preservation: every derivable triple holds at rlExtend i A in every interpretation meeting RlConditions OWL/RLSemantics.lean OWL.RL.Derives ✅ PROVED (2026-08-25) RlReservedFree g (load-bearing: a graph using the reserved vocabulary can make the engine conflate a user blank node with a comprehension witness)
rl_clash_holds_false — a Clash configuration is false under every assignment in every interpretation meeting RlClashConditions OWL/RLSemantics.lean OWL.RL.Clash ✅ PROVED (2026-08-25) AdcMembersIri g (the cax-adc IRI-member narrowing)
rlHerb + rlHerb_conditions — the enriched Herbrand interpretation of a Derives-closed, clash-free fragment graph meets all 79 derivation-row conditions OWL/RLHerbrand.lean RDF/Semantics.lean's herbrand ✅ PROVED (2026-08-26) RlHerbFrag c clauses (a)-(d); saturation hcut; ¬ Clash c
rlHerb_clash_conditions — the same model meets all 13 falsity-headed rows OWL/RLHerbrand.lean OWL.RL.Clash ✅ PROVED (2026-08-26) RlHerbFrag c, ¬ Clash c; cls-maxc1 / cls-maxqc1 / cls-maxqc2 hold VACUOUSLY (their premise needs a literal object, which fragment clause (a) forbids) — stated, not hidden
rlHerb_atom_decode / rlHerb_triple_decode — satisfaction of a ground atom at a non-reserved predicate decodes back to closure membership OWL/RLHerbrand.lean — ✅ PROVED (2026-08-26) non-reserved predicate; triple-term-free object
RlRowId + rlRowRule + owlRlHornSchema + cond_* (66 rows) — the plain Horn rows as DRules, schema membership O(1) by enumeration index, one cond_* per row taking schema satisfaction to the row's RlCond* at restrictInterp Unified/OwlRlSchema.lean satisfies_ruleSentence_iff (Unified/Datalog.lean) ✅ PROVED (2026-08-26) — one direction per row (correction note 17: an iff is FALSE — the sentence quantifies the predicate position over the whole domain, the condition over WfIri) none beyond DRule.wfB
ruleEqRefP / ruleCaxAdcToDw / ruleDtType1 / ruleDtRangeIntersect / ruleXsdAxiom + owlRlFamilySchema + their cond_* — the five guarded and table-indexed families, one rule per instance of a decidable side condition Unified/OwlRlSchema.lean OWL/RLRules.lean tables ✅ PROVED (2026-08-26) —
DNeg + satisfies_negSentence_iff — a falsity-headed row as the universal closure of the negated conjunction of its premises, with ONE satisfaction lemma (the dExclusionSchema / rangeClashSchema pattern at n-ary arity) Unified/OwlRlSchema.lean Unified/DSchema.lean, Unified/RdfsSchema.lean ✅ PROVED (2026-08-26) — full iff atom well-formedness (colon discipline)
RlNegRowId + rlNegRowRule + negCaxAdc + owlRlClashSchema + ncond_* — 12 plain clash rows plus the cax-adc distinct-IRI-pair family Unified/OwlRlSchema.lean OWL.RL.Clash ✅ PROVED (2026-08-26; cls-maxc1 / cls-maxqc1 / cls-maxqc2 added same day) —
DTerm.lit + dlit + dlit_val — a DTerm constructor carrying an RDF.WfLiteral, toCl = embedTerm (.literal l), val = the denotation restrictInterp gives iLit. What makes a cardinality-literal row an ordinary DAtom Unified/Datalog.lean, Unified/OwlRlSchema.lean Unified/RdfEmbed.lean's embedTerm; Unified/RdfTransport.lean's restrictInterp ✅ PROVED (2026-08-26) — dlit_val is rfl none in the model-theoretic layer (DTerm.wfB holds of every literal)
DTerm.litFreeB / DAtom.litFreeB / DRule.litFreeB — the Herbrand-universe restriction the new constructor forces on the OPERATIONAL layer. herbInterp's domain is the constant names, so a rigid literal term denotes outside it; herb_holds_iff, herb_ground_mem_iff, herb_satisfiesSchema, datalog_lfp_complete and datalog_lfp_iff_entails now carry it as a hypothesis Unified/Datalog.lean — ✅ PROVED (2026-08-26) — hypothesis discharged BY COMPUTATION at every existing call site (tripleFact_litFree, graphFacts_litFree, rifTmD_litFree, rifAtomD_litFree, rifDRules_litFree, rifTripleFact_litFree, rifGraphFacts_litFree), so no landed gate theorem weakens in substance literal-freeness of the program's rules, of the facts, and of the queried atom
owlRlSchema_cardinality_rows — cls-maxc2, cls-maxc1, cls-maxqc1 and cls-maxqc2 follow from SatisfiesSchema i owlRlSchema ALONE, with no appeal to OwlRlInterpCond. The shrinkage of the condition bundle, stated as a theorem rather than left in a definition Unified/OwlRlSchema.lean OWL/RLSemantics.lean's RlCondClsMaxc2, RlNCondClsMaxc1/qc1/qc2 ✅ PROVED (2026-08-26) — soundness direction (schema → condition), the same direction and strength as every other cond_* row none beyond schema satisfaction
owlRlSchema + OwlRlInterpCond + owlRlSchema_conditions — the schema (Horn ∪ guarded families ∪ list-valued families ∪ clash) and the bridge delivering the FULL RlConditions / RlClashConditions pair, taking the THREE remaining unexpressible rows as an interpretation-class condition Unified/OwlRlSchema.lean Unified/Theory.lean's EntailsSchema bundle slot ✅ PROVED (2026-08-26) OwlRlInterpCond = cax-dw-comp ∧ cls-maxqc1-comp ∧ minc1-comp (nine conjuncts before 2026-08-26; correction notes 18, 21)
nmAt / seqVal / seqIs_walk / semChain_vals / semShares_vals / spo2Rule / keyRule / owlRlSeqSchema — prp-spo2 and prp-key as per-length Horn families: one rule per collection length, the rdf:first/rdf:rest walk and the chain (or shared-value) premises flattened into m+1 body atoms, the last cell resting at rdf:nil. Generated names carry their index as a unary tail, so the valuation reads the index off a list length and no numeral injectivity is needed Unified/OwlRlSchema.lean OWL/Semantics.lean's SeqIs; OWL/RLSemantics.lean's SemChain / SemShares ✅ PROVED (2026-08-26) — spo2Rule_wf / keyRule_wf for all lengths; cond_prpSpo2 / cond_prpKey in the soundness direction, the same direction as every other cond_* row —
owlRlSchema_seq_rows — prp-spo2 and prp-key follow from SatisfiesSchema i owlRlSchema ALONE, with no appeal to OwlRlInterpCond Unified/OwlRlSchema.lean RlCondPrpSpo2, RlCondPrpKey ✅ PROVED (2026-08-26) — soundness direction none beyond schema satisfaction
allTrue_satisfies_seq / spo2Counter / seqSchema_not_everything — NON-VACUITY for the two families: the all-true interpretation satisfies them at every length, and spo2Counter meets every premise of prp-spo2 at chain length 1 while denying its conclusion, so owlRlSeqSchema is not the everything-relation Unified/OwlRlAdequacy.lean — ✅ PROVED (2026-08-26) non-vacuity guards
unified_owlRl_sound — the stage 4 soundness gate: OWL.RL.Derives g t → EntailsSchema OwlRlInterpCond owlRlSchema [rdfToTheory g] (rdfToTheory [t]) Unified/OwlRlAdequacy.lean rl_derives_holds + satisfies_rdfToTheory_restrict ✅ PROVED (2026-08-26) RlReservedFree g; conclusion is the EXISTENTIAL closure rdfToTheory [t] because the comprehension rows mint blank nodes
unified_owlRl_clash_unsat / unified_owlRl_clash_entails_all — a clashing graph has no model in the schema class, so its translation entails everything Unified/OwlRlAdequacy.lean rl_clash_holds_false ✅ PROVED (2026-08-26) AdcMembersIri g, OwlRlInterpCond
OwlRlEntailsMt + owlRl_complete_ground — ground completeness in CONDITION-BUNDLE form: on the Herbrand fragment, a ground triple at a non-reserved predicate entailed under RlConditions + RlClashConditions is a triple of the saturated closure Unified/OwlRlAdequacy.lean rlHerb_conditions, rlHerb_clash_conditions, rlHerb_triple_decode ✅ PROVED (2026-08-26) — condition-bundle form only (correction note 19) saturation; RlHerbFrag c; ¬ Clash c; triple ground, triple-term-free, non-reserved predicate
allTrue_satisfies_horn / allTrue_satisfies_family / allTrue_violates_clash / allFalse_satisfies_clash / allFalse_violates_horn — SEPARATING models: the all-true interpretation satisfies the Horn and family parts and violates the clash part; the all-false interpretation does the reverse (cls-thing has a premise-free head). Neither half is vacuously satisfied Unified/OwlRlAdequacy.lean — ✅ PROVED (2026-08-26) non-vacuity guards
SCHEMA-RELATIVE GROUND COMPLETENESS NOT PROVED — unified_owlRl_complete_ground over EntailsSchema … owlRlSchema needs liftInterp (rlHerb c) to satisfy every row sentence; liftInterp reads a predication as r.iext p.2 x.2 y.2, so satisfaction quantifies the PREDICATE position over the whole domain, where RlCond* quantifies over WfIri. True for rlHerb c, but a second pass over all 79 rows — and since 2026-08-26 the pass also has to cover the two per-length families at every length, which is an induction on collection length rather than a finite row walk — design doc §4.4 ⬜ OPEN (recorded gap, correction notes 19, 21) —
THREE ROWS NOT IN THE SCHEMA (was nine before 2026-08-26) — cax-dw-comp, cls-maxqc1-comp, minc1-comp: existential heads, excluded by DRule.definiteB; costed and DECIDED against in the next row. Carried by OwlRlInterpCond, named in the soundness statement. prp-spo2 and prp-key left this residue the same day as per-length families; a ternary reserved helper predicate would also have worked (DAtom is n-ary and the helper need never appear in restrictInterp i — the earlier "a helper predicate cannot name it" was wrong) but liftInterp reads rel p args as False at every arity other than 2, so a ternary head is false at every model schema-relative completeness needs Unified/OwlRlSchema.lean module header design doc §4.4 ⬜ OPEN (recorded boundary, correction notes 18, 21) —
EXISTENTIAL HEADS IN THE SCHEMA — DECIDED, NOT BLOCKED. A Schema is a predicate on CL.Sentence, so an existentially headed sentence CAN be in one; the decision is not to. Two costs: (i) the head of RlCondCompDw is not a conjunction of atoms (CompProps carries two universally quantified implications and a five-variable one), so each row is a bespoke CL sentence with a bespoke satisfaction lemma; (ii) an existential head removes the least-model property the completeness direction of the stage-3 class rests on (datalog_lfp_complete; the rdfD1Shape_not_wf exclusion pins the same boundary at the program layer). If admitted later, it should be a SEPARATE sub-schema so the definite owlRlSchema stays available for completeness Unified/OwlRlSchema.lean section header design doc §4.4 ⬜ DECISION RECORDED (2026-08-26, correction note 21) —
HERBRAND FRAGMENT NARROWNESS — RlHerbFrag clause (a) (every object an IRI or blank node) excludes every graph whose closure carries a cardinality literal. Because the minc1 comprehension row emits owl:minCardinality "1" for each owl:ObjectProperty declaration, the completeness direction does not reach graphs declaring object properties. DTerm.lit does NOT widen this (measured 2026-08-26, contrary to the expectation recorded in #613 item 3): clause (a) exists for eq-ref object form, where rlHerb's iext reads "the triple is in the graph" and so needs the object to be an RDF.Subject, which RDF 1.1 Concepts §3.1 forbids a literal to be. frag_obj_subject is consumed at fifteen sites of rlHerb_conditions. Widening needs a different rlHerbIext for the owl:sameAs row, after which rlHerb_triple_decode would decode an atom OWL.RL.Derives cannot produce OWL/RLHerbrand.lean module header, Unified/OwlRlAdequacy.lean module header design doc §4.4 ⬜ OPEN (recorded boundary, correction notes 20, 21) —

Stage 5 (landed 2026-08-26): OWL 2 Direct Semantics and the tableau under the unified theory — Unified/OwlDlDirect.lean (the name vocabulary, the class-expression translation, the transport pair and the transfer lemmas) and Unified/OwlDlAdequacy.lean (the gate theorems). Design-doc correction notes 21-26 record the deviations. Axiom audit on every row below: propext, Classical.choice, Quot.sound only (in-source #print axioms); sem_inl uses propext, Quot.sound only.

Scope: the SHIQ fragment of OWL/Tableau.lean — class names, owl:Thing, owl:Nothing, ObjectComplementOf, binary ObjectIntersectionOf / ObjectUnionOf, ObjectAllValuesFrom, ObjectSomeValuesFrom, and ObjectMin/MaxCardinality in both the unqualified and the qualified form; ClassAssertion, ObjectPropertyAssertion, binary DifferentIndividuals; a role box of subrole axioms and transitivity declarations. This is the whole of OWL.Concept / OWL.Assertion / OWL.RoleAxioms, so no fragment hypothesis appears in any statement.

Equality enters the unified theory here for the first time (stages 1-4 never used CL.Sentence.eq): distinctBlock is the sentence family indexed by n carrying pairwise negated equations, and .diff translates to a single negated equation.

Theorem Module Native anchor Status Fragment / hypotheses
dlName / dlDecode / indName / className / roleName + dlName_has_colon, dlDecode_dlName, dlDecode_dlName_ne, dlName_injective — the colon-carrying injective encoding that separates the tableau's three name spaces from each other and from the colon-free bound-name space of Unified/RdfEmbed.lean Unified/OwlDlDirect.lean Unified/RdfEmbed.lean's escape / unescape ✅ PROVED (2026-08-26) — unconditional none (correction note 21)
bvar / bvars / OkArg + bvar_injective, bvar_no_colon, bvars_nodup, okArg_ne — the bound-variable space and the argument-position invariant the counting translation threads Unified/OwlDlDirect.lean — ✅ PROVED (2026-08-26) —
conceptFormula / assertionSentence / subRoleSentence / transSentence / roleAxiomSentences / owlDlDirect — the Direct-Semantics translation (OWL 2 Direct Semantics Table 5, restricted to the fragment); cardinality as a first-order counting formula Unified/OwlDlDirect.lean OWL.Interp.sem ✅ DEFINED (2026-08-26) correction note 22 (the role-axiom sentences are included ONCE, inside owlDlDirect)
DLCompat + restrictInterpDL + dlCompat_restrict — the restriction half of the transport pair; the domain is PRESERVED (there is no literal operator whose arguments must be recovered from denotations) Unified/OwlDlDirect.lean Unified/RdfTransport.lean's restrictInterp ✅ PROVED (2026-08-26) — compatibility by Iff.rfl —
inlInterp + all_inl + card_sum_iff + sem_inl + liftInterpDL + dlCompat_lift — the lift half. Domain δ ⊕ String: the right summand carries predicate identity and both extensions are FALSE on it, so every counting witness lies in the left summand where distinctness is distinctness in δ Unified/OwlDlDirect.lean OWL.Interp ✅ PROVED (2026-08-26) the Option String × idom tag-product of stage 1 is UNSOUND here — correction note 23
sat_conceptFormula — THE transfer lemma: a DLCompat pair reads every class expression of the fragment the same way, at every FreshVal valuation and every OkArg argument. Induction over OWL.Concept, 12 cases; the four counting cases go through sat_exBlock_card Unified/OwlDlDirect.lean OWL.Interp.sem, OWL.Interp.succWitness ✅ PROVED (2026-08-26) — full iff FreshVal i ν, OkArg k x (both discharged unconditionally at the top level)
sat_exBlock_card / sat_exBlock_card0 / exists_fun_map / exists_override_map / sat_distinctBlock — the counting machinery: an existential over CL VALUATIONS becomes the existential over pairwise-distinct witness LISTS that succWitness uses, because every list of the right length is the image of a duplicate-free name list under some valuation Unified/OwlDlDirect.lean — ✅ PROVED (2026-08-26) — full iff bvars duplicate-free (proved)
satisfies_assertionSentence / satisfies_subRoleSentence / satisfies_transSentence / satisfiesAll_roleAxiomSentences / satisfiesAll_owlDlDirect_iff — the whole translation transfer, stated ONCE against DLCompat and instantiated in both directions (correction note 24) Unified/OwlDlDirect.lean OWL.Satisfies, OWL.SatAll, OWL.RespectsRBox ✅ PROVED (2026-08-26) — full iff —
unified_adequate_dl — the stage 5 gate: (∃ i : CL.Interp, CL.SatisfiesAll i (owlDlDirect R A)) ↔ OWL.Consistent R A Unified/OwlDlAdequacy.lean OWL.Consistent (OWL/Tableau.lean) ✅ PROVED (2026-08-26) — FULL iff, NO side conditions none
refuted_unified_unsat — the stage 5 gate, refutation side: OWL.Refuted R A → the translation has no CL model. Covers the whole clash calculus: clash, botClash, minMaxClash, maxClash, disjSplit (branching), minMaxClashQ, maxClashQ, minQMaxClash, leqMerge (the ABox-rewriting ≤-rule) and exWitness (the fresh-individual ∃-rule) Unified/OwlDlAdequacy.lean OWL.refuted_not_consistent / OWL.refuted_sound ✅ PROVED (2026-08-26) OWL.Refuted R A only
refuted_unified_entails_all — explosion: a refuted ABox's translation entails every CL sentence Unified/OwlDlAdequacy.lean — ✅ PROVED (2026-08-26) OWL.Refuted R A
unified_sat_not_refuted / consistent_unified_sat — the two directions of the three-valued verdict contract of #586: "inconsistent" ⇒ unified-unsatisfiable, "consistent" (model exhibited) ⇒ unified-satisfiable; "unknown" claims nothing Unified/OwlDlAdequacy.lean — ✅ PROVED (2026-08-26) —
nearClashAbox_consistent / clashAbox_refuted / diff_flips_satisfiability — SEPARATING PAIR: two ABoxes differing by ONE owl:differentFrom assertion, one unified-satisfiable and one unified-unsatisfiable. Refuted therefore holds of something (refuted_unified_unsat is not vacuous) and the equality machinery is what carries the verdict Unified/OwlDlAdequacy.lean OWL.Refuted.maxClash ✅ PROVED (2026-08-26) non-vacuity guards
empty_unified_sat / empty_not_refuted / nearClash_not_entails_false / conj_entails_component / subRole_entails_super — the calculus does not refute everything; a satisfiable ABox's translation has not collapsed; the translation carries real content on both the concept side and the role-box side Unified/OwlDlAdequacy.lean — ✅ PROVED (2026-08-26) non-vacuity guards
disjointGoal_iff / propDisjointGoal_iff / rangeGoal_iff / domainGoal_iff / diffGoal_iff / sameGoal_iff — the six negation-goal laws: an interpretation REFUTES DisjointClasses / DisjointObjectProperties / ObjectPropertyRange / ObjectPropertyDomain / DifferentIndividuals / identity exactly when it MODELS the goal OWL/NegationGoals.lean builds for it OWL/TableauTheorems.lean OWL 2 Direct Semantics Tables 5-6; OWL/NegationGoals.lean builders ✅ PROVED (2026-09-07) — full iff each propext, Classical.choice, Quot.sound
canon_models_all / canon_rejects_bot — MODEL EXISTENCE, literal fragment: a label set of literals (atom, negated atom, owl:Thing) with no atom clash is satisfied by its own canonical one-element interpretation, and owl:Nothing is in no model. Together: Refuted.clash and Refuted.botClash are exactly the obstructions to a model at the leaf. The converse direction to refuted_sound, restricted to the leaf OWL/TableauTheorems.lean OWL.Refuted.clash, OWL.Refuted.botClash ✅ PROVED (2026-09-07) NONE — axiom-free
GOAL-BUILDER SOUNDNESS AT THE GRAPH LEVEL NOT PROVED — the six laws above are about the FORM of the axiom and its goal. Lifting them to OWL/NegationGoals.lean's RDF-graph builders needs a Direct-Semantics satisfaction relation on Graph, which this tree does not have — design record 2026-09-07-lean-owl-corpus-gap.md § 11.3 ⬜ OPEN (recorded obligation) —
SEARCH COMPLETENESS NOT PROVED, WITH A FRAGMENT AND TWO BLOCKERS — the target is ALC ABox consistency over an acyclic TBox with role depth ≤ 3 and ≤ 6 existentials per node, the fragment where Refute.lean's caps (maxWitnessDepth = 3, maxGeneratedWitnesses = 6) do not bite. Blocked on (1) the inductive lift from canon_models_all through ⊓ / ⊔ / ∀ / ∃, (2) Refute.search being a partial def with no induction principle, which needs a fuel-indexed reformulation and an agreement proof like RLClosureIndexed.indexedClosure_eq — design record 2026-09-07-lean-owl-corpus-gap.md § 11.4 ⬜ OPEN (stated theorem + plan) —
TABLEAU COMPLETENESS NOT PROVED — ¬ OWL.Consistent R A → OWL.Refuted R A. OWL/Tableau.lean's Refuted has no blocking condition and no ⊔-saturation strategy, and OWL/TableauTheorems.lean proves soundness only. The gate is soundness plus the satisfiability ↔, not a decision procedure — design doc §4.5 ⬜ OPEN (recorded gap, correction note 26) —
FRAGMENT BOUNDARY — OWL 2 DL constructs OUTSIDE OWL.Concept / OWL.Assertion: nominals (ObjectOneOf, ObjectHasValue), datatypes and data properties (DataSomeValuesFrom, DataAllValuesFrom, data cardinality, DatatypeRestriction), functional and inverse-functional roles, inverse roles, role chains (ObjectPropertyChain), reflexive/irreflexive/asymmetric/disjoint role axioms, Self restrictions, and every TBox axiom other than the role box (SubClassOf, EquivalentClasses, DisjointClasses). OWL/Tableau.lean's header names the first three as not ported; the rest were never in that datatype OWL/Tableau.lean module header OWL 2 Direct Semantics Tables 5-7 ⬜ OPEN (recorded boundary) —
DL-SPECIES GUARD DOES NOT ATTACH — OWL/SyntaxDL.lean's speciesIsDl takes five RDF Graphs and decides OWL 2 DL membership from triples. The Direct-Semantics route does not factor through graphs (§5.3) and the tree has NO reader from Graph to List OWL.Assertion, so the guard has no input to guard. The stage 5 fragment guard is structural instead: OWL.Concept IS the fragment Unified/OwlDlDirect.lean module header OWL/SyntaxDL.lean ⬜ OPEN (recorded boundary, correction note 25) —
DIRECT-vs-RDF-BASED CORRESPONDENCE NOT PROVED — the two OWL 2 semantics sit side by side over the same CL.Interp (owlRlSchema on the graph route, owlDlDirect on the structural route). The OWL 2 correspondence theorem (RDF-Based Semantics §7.2) relating them on mapped ontologies is NOT machine-checked here, as §5.3 already states — design doc §5.3 ⬜ OPEN (recorded boundary, unchanged from §5.3) —

Stage 6 (landed 2026-08-26): SPARQL 1.x basic graph patterns and the entailment regimes as satisfaction queries over the unified theory — Unified/SparqlQuery.lean (the definitions, the instantiation bridge, the regime table) and Unified/SparqlAdequacy.lean (the theorems). Design-doc correction notes 27-32 record the deviations. Axiom audit on every row below: propext, Classical.choice, Quot.sound only (21 in-source #print axioms).

Three delimitations carried by every row. (1) SPARQL 1.1 §18.3.1: the engine matches a PATTERN BLANK NODE as a constant with that label, where the specification's pattern instance mapping makes it a non-distinguished variable that may match any RDF term (#607). The narrowing agrees with the Skolem reading, so the gate is a full iff — but on a pattern containing a blank node it is adequate TO THE ENGINE AT evalBgp, not to the specification. It is no longer a fragment restriction on what can be CLAIMED: the query path rewrites pattern blank nodes into non-distinguished variables before evaluation, so unified_adequate_bgp_spec states the gate over the rewritten pattern with the blank-node guard PROVED (bgpBnodeFree_rewriteBnodes) rather than assumed. It replaces unified_adequate_bgp_bnodeFree, whose guard did no work in the proof. (2) NO MULTIPLICITY CLAIM (design doc §5.4): Answers is a Prop, the evaluator side is MEMBERSHIP, and nothing downstream supplies the bag either — SPARQL/AlgebraSpec.lean keeps the §18.5 set and cardinality layers apart and the F* bag-refinement proof is not ported. (3) RDF.Term.eqb is COARSER than syntactic identity (language-tag case, rdf:XMLLiteral canonical XML) and Graph.mem is stated over it, so answers are evaluated under termEqSchema.

Theorem Module Native anchor Status Fragment / hypotheses
varName + varName_no_colon, varName_ne_iri, varName_ne_bnodeName, varName_injective — the reserved ?-prefixed colon-free variable spelling, fresh against every well-formed IRI and disjoint from the _-initial bound blank-node names Unified/SparqlQuery.lean Unified/RdfEmbed.lean's escape ✅ PROVED (2026-08-26) — unconditional —
embedPatternTerm / embedPatternSubject / patternAtom / bgpBody / UQuery / UQuery.body / UQuery.instantiate / sparqlBgpToQuery / Answers Unified/SparqlQuery.lean SPARQL/Algebra.lean Bgp ✅ DEFINED (2026-08-26) UQuery carries the PATTERN, not an arbitrary CL.Sentence — correction note 28
patternAtom_eq_tripleAtom — the bridge: instTriple id μ tp = some t → patternAtom μ tp = tripleAtom t, a syntactic EQUALITY, so the model theory never reasons about pattern syntax Unified/SparqlQuery.lean SPARQL.instTriple (SPARQL/Update.lean), constructPredicate (SPARQL/Query.lean) ✅ PROVED (2026-08-26) — equation, no hypotheses beyond the instantiation —
termEqSchema + denot_embedTerm_congr_of_schema — the engine-term-equality schema (one eq row per Term.eqb-equal pair), and reading it back as denotation equality Unified/SparqlQuery.lean RDF.Term.eqb (RDF/Core.lean) ✅ PROVED (2026-08-26) delimitation 3; LBase §2.4 axiom-schema mechanism, same device as dValueSchema
satisfies_rdfToTheorySk_iff / satisfies_rdfToTheorySk_restrict — the Skolem reading transported: RDF 1.1 Semantics §6, so HoldsAll at a FIXED assignment, not Satisfies Unified/SparqlAdequacy.lean RDF.HoldsAll (RDF/Semantics.lean) ✅ PROVED (2026-08-26) — full iff NONE (FreshVal i i.iName is trivial)
sat_tripleAtom_eqb / sat_tripleAtom_of_graphMem — a predication about an ENGINE-equal triple transfers, under termEqSchema Unified/SparqlAdequacy.lean RDF.Graph.mem, RDF.Subject.eqb_eq ✅ PROVED (2026-08-26) — full iff SatisfiesSchema i termEqSchema
bgp_eval_sound (via evalBgpFrom_matches) — every mapping evalBgp b g returns instantiates the WHOLE pattern into the graph: BgpMatches μ b g Unified/SparqlAdequacy.lean SPARQL.tpMatch_inst, instTriple_mono, evalBgpFrom_extends (SPARQL/BgpRefinement.lean) ✅ PROVED (2026-08-26) — UNCONDITIONAL none
bgp_matches_answers — the pivot entails the instantiated body Unified/SparqlAdequacy.lean — ✅ PROVED (2026-08-26) — UNCONDITIONAL none
TermQ / tq / tq_eq_iff / herbDenot / herbQ — RDF terms modulo Term.eqb (Quot.mk for the identification, Quot.lift of the eqb predicate for the converse) and the term model in which true means "a triple of g" Unified/SparqlAdequacy.lean RDF.Term.eqb_refl / _symm / _trans (RDF/Core.lean); replaces RDF.herbrand, which separates a language tag by case ✅ PROVED (2026-08-26) — full iff on the quotient —
herbQ_satisfiesSchema / herbQ_satisfies_sk — the term model satisfies termEqSchema and the graph's Skolem reading Unified/SparqlAdequacy.lean — ✅ PROVED (2026-08-26) _sk needs RDF.GraphTtFree g
herbDenot_of_eqb / eqb_of_herbDenot — the model identifies exactly what Term.eqb identifies Unified/SparqlAdequacy.lean — ✅ PROVED (2026-08-26) — both directions eqb_of_herbDenot: TermTtFree on both terms
instSubject_of_denot / instObject_of_denot / constructPredicate_of_denot / patternAtom_reflect — reflection: a satisfied predication forces the pattern to instantiate to a graph triple. The tag component REFUTES an unbound variable, a literal in subject position and a blank node in predicate position Unified/SparqlAdequacy.lean SPARQL.instSubject / instObject / constructPredicate ✅ PROVED (2026-08-26) PatternSubjectTtFree / PatternTermTtFree; TermTtFree on the graph's object
unified_adequate_bgp — the stage 6 gate: BgpMatches μ b g ↔ Answers condTrue termEqSchema [rdfToTheorySk g] (sparqlBgpToQuery b) μ Unified/SparqlAdequacy.lean SPARQL/Algebra.lean evalBgp (through bgp_eval_sound), RDF.herbrand's quarantine discipline ✅ PROVED (2026-08-26) — FULL iff → UNCONDITIONAL; ← RDF.GraphTtFree g + BgpTtFree b. NO domain hypothesis on μ (an unbound variable is refuted, not excluded). Delimitations 1-3
patternTermBnodeFree_rewrite / patternSubjectBnodeFree_rewrite / bgpBnodeFree_rewriteBnodes — the pattern Query.evalSelect hands the algebra, b.map rewriteBnodeTriple, carries NO blank node Unified/SparqlAdequacy.lean SPARQL.rewriteBnodeTriple (SPARQL/Query.lean) ✅ PROVED (2026-08-26) — equation BgpTtFree b (the one position the rewrite does not descend into is a triple-term SUBJECT, which matches no data subject)
patternTermTtFree_rewrite / patternSubjectTtFree_rewrite / bgpTtFree_rewriteBnodes — the rewrite preserves triple-term freeness Unified/SparqlAdequacy.lean — ✅ PROVED (2026-08-26) BgpTtFree b
unified_adequate_bgp_spec — the §18.3.1 gate with NO blank-node hypothesis: BgpMatches μ (b.map rewriteBnodeTriple) g ↔ Answers … (sparqlBgpToQuery (b.map rewriteBnodeTriple)) μ for EVERY b, blank nodes and all. REPLACES unified_adequate_bgp_bnodeFree, whose bgpBnodeFree b = true hypothesis did no work in the proof and merely marked which instances were specification claims; the guard is now DISCHARGED by bgpBnodeFree_rewriteBnodes instead of assumed Unified/SparqlAdequacy.lean #607 ✅ PROVED (2026-08-26) — FULL iff RDF.GraphTtFree g + BgpTtFree b and nothing else. Delimitations 2-3 still apply
wBbn_rewrite_bnodeFree / wBbn_not_bnodeFree / unified_bgp_bnode_answer_witness / unified_bgp_bnode_no_answer + two #guards — non-vacuity for the dropped guard: ?s <p> _:z FAILS bgpBnodeFree, its rewrite passes, bgpMatchesCheck is FALSE for the raw pattern and TRUE for the rewritten one on the same mapping, and the gate yields both a real answer and a refuted one Unified/SparqlAdequacy.lean — ✅ PROVED (2026-08-26) non-vacuity guards
BindingCompat + tryBindSubject_complete / tryBindTerm_complete / tpMatch_complete / evalBgpFrom_complete / bgp_eval_complete — evaluator completeness, the direction SPARQL/BgpRefinement.lean does not have: a mapping that instantiates the pattern into the graph has a counterpart the evaluator RETURNS, agreeing on every variable of the pattern up to Term.eqb Unified/SparqlAdequacy.lean SPARQL.evalBgpFrom, Extends (SPARQL/BgpRefinement.lean) ✅ PROVED (2026-08-26) BgpTtFree b. Conclusion is AGREEMENT, not μ ∈ evalBgp b g — correction note 27
unified_adequate_bgp_engine / unified_bgp_answers_returned — the two chains: engine answer ⇒ unified answer (unconditional); unified answer ⇒ a returned mapping agreeing with it Unified/SparqlAdequacy.lean — ✅ PROVED (2026-08-26) _returned: GraphTtFree g, BgpTtFree b
regimeToSchema / regimeDispatchSchema — the specification table (the four W3C names of RDF.Regime.ofName?, x-rdfscore, x-rdfsplus, the x-ikl-* family) and, separately, what RDFS.entailmentClosureForQueryExt ACTUALLY selects. A #guard pins the disagreement: the ENGINE dispatcher sends "RDFS" (and every other unrecognised string) to OWL.RL.closure Unified/SparqlQuery.lean RDFS/RegimeDispatch.lean module header ✅ DEFINED + PINNED (2026-08-26) correction notes 29, 30
regime_sound_of_closureHolds — the design document's one regime-soundness shape, stated once: a materialisation-based regime's answers are unified answers over the ORIGINAL graph whenever its closure holds in every model of the graph meeting the regime's schema Unified/SparqlAdequacy.lean design doc §4.6 ✅ PROVED (2026-08-26) the hclosure premise is the regime's own content
regime_sound_simple — regime simple: RDF.Regime.closure .simple = id, so its answers are answers under the empty schema Unified/SparqlAdequacy.lean RDF.Regime.closure (RDF/Entailment.lean) ✅ PROVED (2026-08-26) — soundness none
regime_sound_rhoDf — regime x-rdfscore (the ρdf closure): every answer over the closure is an answer over the graph under rdfsCoreSchema Unified/SparqlAdequacy.lean RDF.rhoDf_derives_holds (Unified/RhoDfSchema.lean), RDFS.closure_sound ✅ PROVED (2026-08-26) — soundness none
regime_rhoDf_answers_closure_iff — regime x-rdfscore, the materialisation is ANSWER-PRESERVING: under the ρdf schema, answering from the closure's Skolem reading and from the graph's are the SAME relation. The Skolem-level analogue of RDF.rhoDfEntails_closure_iff Unified/SparqlAdequacy.lean RDF.rhoDfEntails_closure_iff ✅ PROVED (2026-08-26) — FULL iff, both directions none
regime_sound_rdfs — regime RDFS (RDF.Regime.closure .rdfs, the full closure) Unified/SparqlAdequacy.lean RDF.derivesFull_holds, RDF.axiomaticTriples_hold, RDFS.fullClosure_sound ✅ PROVED (2026-08-26) — soundness ∀ c ∈ cmps, RDF.IsRdfMemberIri c; rdf:XMLLiteral ∈ D (stage 2 note 10b)
embed_entails_asserted_merge + asserted_merge_sound — ASSERTION-DECORATION MERGE SOUNDNESS AGAINST THE UNIFIED LAYER'S OWN DATASET READING: datasetToTheory ds entails the whole merged default graph mergeAsserted computes, and an engine answer over that graph is a unified answer from the dataset's embedding. Condition: CL.IklRespectsThat alone. RESTATED 2026-08-26 (#609 item 3) — until then both were stated in Unified/SparqlAdequacy.lean over iklPremises, a reading that asserts EVERY named graph. RENAMED and GENERALISED 2026-08-26 (#626): they were ikl_extend_entailed / regime_sound_ikl over the deleted CL.IklRegime.extendDataset, whose selection predicate also tested a content-address graph-name prefix. No proof used that prefix, so the statements are now over the assertion decoration alone — strictly more general Unified/ClBridge.lean Unified/DatasetEmbed.lean graphAsserted, datasetToTheory ✅ PROVED (2026-08-26) — soundness, any asserting subject datasetBnodeNames ds = [] (#guard pins it for the witness, not proved in general)
mergeWhere + mergeWhere_entailed + mergeAll_entailed + asserted_merge_premises_entailed — the SUPERSEDED iklPremises statement holds for EVERY selection predicate over the named graphs, the urn:cl:def:asserts test and the merge-everything predicate alike, so it certified nothing about the CHOICE of predicate and did not see that a link decoration does not assert. This is the measurement that moved merge soundness onto datasetToTheory Unified/ClBridge.lean Unified/DatasetEmbed.lean graphAsserted ✅ PROVED (2026-08-26) — strength delimitation of the superseded statement —
wDsMentioned + embedding_sees_the_assertion_decoration (embedding_entails_content / embedding_refutes_mentioned_content / premises_entail_mentioned_content) — the replacement statement IS predicate-sensitive: over datasetToTheory the proposition's content is entailed on wDs and REFUTED on wDsMentioned — the same dataset with the assertion decoration deleted — while iklPremises entails it on both. #guards pin the executable half (the assertion-decoration merge does not carry the mentioned graph; the merge-everything predicate does) Unified/ClBridge.lean mergeAsserted ✅ PROVED (2026-08-26) — sensitivity, one refutation witness separating model coherentBlind
premises_entail_content + decorationOnly_refutes_content + ikl_reading_diverges_from_decoration_only_embedding — the two renderings of a proposition inside RDF DISAGREED, over the SUPERSEDED embedding: on the witness dataset wDs, iklPremises entails the asserted proposition's content triple and decorationOnlyToTheory does not — and does not under PropAlphaInvariant either. Separating model typeBlindInterp. RESTATED 2026-08-26: these were stated about datasetToTheory, which the #609 item-3 repair changed; the statements are FALSE of the repaired reading (embedding_entails_content) and are kept about the reading they were evidence against — the topLevel_exclusion_insufficient_for_tt pattern of the #602 repair Unified/ClBridge.lean Unified/DatasetEmbed.lean decorationOnlyToTheory ✅ PROVED (2026-08-26) — refutation witness, #609 item 3 wDs was a real CL.toRdfDataset output until #626 deleted that translator; it is now built from plain IRIs in ClBridge.lean §2. Premise AND conclusion each shown satisfiable
decorationOnly_refutes_content_ikl — the divergence survived CL.IklRespectsThat, and this row names the defect exactly: coherence constrains a proposition's ZERO-ARY relation extension, and the superseded embedding put the that-term in the second argument of urn:cl:def:names, where coherence says nothing. The repair moved the that-term into the zero-ary position Unified/ClBridge.lean CL.IklRespectsThat (CL/Semantics.lean) ✅ PROVED (2026-08-26) — refutation witness about the superseded reading separating model coherentBlind
pDen / pSeq / pSat / pAll / pAny / pForall / pExists + pSat_eq + propModel + propModel_coherent — the first IKL-coherent interpretation in the tree. Domain Prop; a proposition IS a Prop; pSat writes the model's own satisfaction out as a recursion, which breaks the circularity between CL.Sat and Interp.iProp. pSat_eq proves the recursion agrees with CL.Sat clause by clause Unified/ClBridge.lean CL.Sat, CL.IklRespectsThat (CL/Semantics.lean) ✅ PROVED (2026-08-26) — satisfiability witness propModel_coherent needs ∀ p, R p [] ↔ p (zero-ary predication transparent)
embed_asserts_decorated_graphs + embedding_entails_content + ikl_reading_agrees_with_dataset_embedding — the repair, as a DERIVATION: under CL.IklRespectsThat ALONE, an interpretation satisfying datasetToTheory ds satisfies the content of every named graph the default graph decorates with urn:cl:def:asserts. The encoding commitment is therefore a theorem, not a condition. REMOVED with this landing: IklAssertionCommitment (the adopted condition) and commitment_not_derivable (its non-derivability), both of which described the superseded embedding Unified/ClBridge.lean Unified/DatasetEmbed.lean graphAsserted; CL.sat_assert_that ✅ PROVED (2026-08-26) — derivation, replaces an adopted condition datasetBnodeNames ds = [] (#guard pins it for the witness, not proved in general)
objModel / objModel_respectsThat / embed_entails_asserted_merge_nonvacuous / objModel_not_everything, decorationOnly_strictly_weaker, divergence_premise_satisfiable / _conclusion_satisfiable — non-vacuity: objModel is IKL-coherent, satisfies the REPAIRED embedding of the witness dataset, and refutes a sentence; coherentBlind satisfies the superseded reading of the same dataset and refutes the repaired one, so the refinement datasetToTheory_entails_decorationOnly is proper Unified/ClBridge.lean — ✅ PROVED (2026-08-26) non-vacuity guards
Merge soundness RELATIVE TO iklPremises — CLOSED 2026-08-26 by changing the embedding, not the condition. It is now stated over [datasetToTheory ds] under CL.IklRespectsThat and is sensitive to the urn:cl:def:asserts test. What is NOT claimed: anything outside datasetBnodeNames ds = [], and completeness in either direction Unified/ClBridge.lean #609 item 3 ✅ CLOSED (2026-08-26, correction note 33) fragment guard above
CL/IKL EXECUTABLE STACK — SATISFACTION HALF NOW PROVED, READER HALF A FRAGMENT STATEMENT. #609 item 1 is closed by satFin_eq and item 2 is bounded by CL/ClifAdequacy.lean; both are itemised in the block below. Item 3 (toRdf_adequate) is the Unified/ClBridge.lean rows above CL/FiniteSatTheorems.lean, CL/ClifAdequacy.lean #609 🟡 PARTLY CLOSED (2026-08-26) see the block below
bgpMatches_append / satisfies_bgpBody_append / answers_bgp_append_iff — §18.5's JOIN is the conjunction on the EntailsSchema side: answering the concatenated pattern is answering both parts Unified/SparqlAlgebra.lean SPARQL/AlgebraSpec.lean InJoin ✅ PROVED (2026-08-26) — FULL iff NONE. Holds for every condition bundle, schema and premise list
unified_adequate_join / unified_adequate_join_conj — the JOIN gate: (BgpMatches μ b₁ g ∧ BgpMatches μ b₂ g) ↔ Answers … (sparqlBgpToQuery (b₁ ++ b₂)) μ Unified/SparqlAlgebra.lean #614 ✅ PROVED (2026-08-26) — FULL iff RDF.GraphTtFree g, BgpTtFree b₁, BgpTtFree b₂ — the stage 6 gate's own guards and no others. No blank-node hypothesis. No multiplicity
bgpMatches_mono / extends_of_isMerge_left / extends_of_isMerge_right / inJoin_bgpMatches / unified_join_answers — a §18.5 join row over two BGP evaluations is a unified answer. This is where merge and compatibility do the work: the merged mapping Extends both arguments, and SPARQL.instTriple_mono carries BgpMatches along Unified/SparqlAlgebra.lean SPARQL/AlgebraSpec.lean merge_extends_left / _right; SPARQL/BgpRefinement.lean instTriple_mono ✅ PROVED (2026-08-26) — UNCONDITIONAL none
unified_join_engine_answers — a row of the RUNNING SPARQL.join is a unified answer Unified/SparqlAlgebra.lean SPARQL/JoinRefinement.lean join_spec_sound ✅ PROVED (2026-08-26) — soundness, hypothesis-carrying ∀ m₁ ∈ Ω₁, ∀ m₂ ∈ Ω₂, Binding.compatible m₁ m₂ → Compatible m₁ m₂ — join_spec_sound's own hypothesis; compatible_not_Compatible_of_coarse is the witness that it cannot be dropped
jCompatible / jInJoin / unified_join_answer_witness / unified_join_no_answer + four #guards — non-vacuity for JOIN: two real evaluator rows sharing a variable, compatible, merging to a row that matches the concatenated pattern; the reversed mapping is refuted Unified/SparqlAlgebra.lean — ✅ PROVED (2026-08-26) non-vacuity guards
bgpDisjBody / AnswersUnion / satisfies_bgpDisjBody_iff / answers_union_of_or — §18.5's UNION as the disjunction of the two instantiated bodies. There is no basic graph pattern whose query is a union, so UQuery cannot carry one and AnswersUnion is entailment of the disjunction Unified/SparqlAlgebra.lean SPARQL/AlgebraSpec.lean InUnion ✅ PROVED (2026-08-26) — answers_union_of_or is ONE DIRECTION, unconditional none
unified_adequate_union — the UNION gate: (BgpMatches μ b₁ g ∨ BgpMatches μ b₂ g) ↔ AnswersUnion … [rdfToTheorySk g] b₁ b₂ μ Unified/SparqlAlgebra.lean #614 ✅ PROVED (2026-08-26) — FULL iff RDF.GraphTtFree g, BgpTtFree b₁, BgpTtFree b₂. The ← direction reads one disjunct off herbQ g; that step is NOT a general property of EntailsSchema — see the row below
entailsSchema_disj_does_not_split — the exact strength of that gate: the premise list [A ∨ B] entails A ∨ B and NEITHER disjunct, with the two term models as the refutations. So unified_adequate_union's ← direction is a property of the premise list [rdfToTheorySk g] (it has a canonical model), not of entailment Unified/SparqlAlgebra.lean — ✅ PROVED (2026-08-26) — refutation witness separating models herbQ uG1, herbQ uG2
extends_of_smapEq / inUnion_bgpMatches / unified_union_answers / unified_union_engine_answers — a §18.5 union row, and a row of the RUNNING SPARQL.union, are unified answers to the union. Unconditional in both cases: union is list append, so there is no compatibility test to be coarse about Unified/SparqlAlgebra.lean SPARQL/AlgebraRefinement.lean occurs_append ✅ PROVED (2026-08-26) — UNCONDITIONAL none
unified_union_answer_witness / unified_union_no_answer + six #guards — non-vacuity for UNION: a real answer over a graph one branch matches, and a refutation over a graph neither matches Unified/SparqlAlgebra.lean — ✅ PROVED (2026-08-26) non-vacuity guards
inFilter_answers / unified_adequate_filter — §18.5's FILTER: a filter row is a unified answer PLUS its side condition, and the gate is a full iff whose filter conjunct is the SAME on both sides. §18.5 states FILTER parametrically in FExpr := SMap → Bool; f μ has no reading in an interpretation, and neither theorem gives it one Unified/SparqlAlgebra.lean SPARQL/AlgebraSpec.lean InFilter ✅ PROVED (2026-08-26) — inFilter_answers UNCONDITIONAL; unified_adequate_filter a full iff under RDF.GraphTtFree g + BgpTtFree b the filter conjunct is carried, not interpreted
embedPatternTerm_congr / embedPatternSubject_congr / patternAtom_congr / bgpBody_congr / answers_congr_onVars — Answers reads the solution mapping ONLY through the pattern's own variables: two mappings agreeing there instantiate the pattern to the SAME sentence (a syntactic equality), hence give the same verdict for every condition bundle, schema and premise list Unified/SparqlAlgebra.lean — ✅ PROVED (2026-08-26) — syntactic equality agreement on bgpVars b
filter_not_determined_by_the_query_sentence — the FILTER negative result. For EVERY function φ from sentences to sentences, every premise list, every bundle and every schema, the claim "φ (bgpBody μ b) is entailed exactly when the filter passes" is FALSE. Witness: two mappings that instantiate the pattern identically, separated by bound(?z) on a variable outside the pattern (§17.4.1.1). So the filter conjunct in unified_adequate_filter cannot be replaced by an entailment and must stay a side condition on the mapping. SPARQL/AlgebraRefinement.lean's FExprCongr is the algebra-layer counterpart, and it assumes congruence up to SMapEq — agreement on ALL variables, not on the pattern's Unified/SparqlAlgebra.lean #614 ✅ PROVED (2026-08-26) — refutation, universally quantified over φ none — the refutation holds for every φ, premise list, bundle and schema
FILTER'S CONDITION HAS NO MODEL-THEORETIC READING HERE — §17's effective boolean value is three-valued (an expression may raise an error) and is defined on RDF terms and their lexical/value spaces, not on the denotations of an interpretation. §18.5 itself states FILTER parametrically for that reason, and this stage keeps it parametric. Internalising §17 is a separate stage, not a missing lemma Unified/SparqlAlgebra.lean module header #614 ⬜ OPEN (recorded boundary, with filter_not_determined_by_the_query_sentence as its proof that the boundary is real) —
UNION CARDINALITY NOT CLAIMED — §18.5's union ADDS multiplicities. InUnion is Occurs in either operand, a Prop, and AnswersUnion is a Prop. SPARQL/AlgebraRefinement.lean's union_card states the cardinality law at the algebra layer; nothing ties it to the unified layer Unified/SparqlAlgebra.lean design doc §5.4 ⬜ OPEN (instance of the multiplicity boundary) —
JOIN COMPLETENESS BACK TO THE ENGINE'S join LIST NOT PROVED — bgp_eval_complete gives, per side, a returned mapping agreeing with μ up to Term.eqb; assembling the two into one row of join needs the engine's Binding.compatible to hold of them, which needs a domain lemma for evalBgp the tree does not have, and the conclusion would still be agreement up to Term.eqb, not membership (correction note 27) Unified/SparqlAlgebra.lean module footer #614 ⬜ OPEN (recorded gap) —
bgpMatchesCheck + bgpMatchesCheck_iff, unified_bgp_answer_witness, unified_bgp_no_answer, termEqSchema_nontrivial — non-vacuity: the pivot decided with BOTH polarities pinned by #guard; a real answer; a REFUTED answer (so the gate is not compatible with Answers holding of everything); a schema row that is not an instance of reflexivity Unified/SparqlAdequacy.lean — ✅ PROVED (2026-08-26) non-vacuity guards
ENGINE MATCHES PATTERN BLANK NODES AS CONSTANTS at evalBgp — tryBindSubject / tryBindTerm require label equality, so every stage 6 row stated over evalBgp or over a raw b is adequate to that entry point, not to §18.3.1. The QUERY path does not go through a raw b: Query.evalSelect / evalAsk / evalConstruct run QueryPattern.rewriteBnodes first, and unified_adequate_bgp_spec is the gate stated over the rewritten pattern with no blank-node hypothesis. The 2026-08-26 repair extended the rewrite to EXISTS bodies, which it did not reach before SPARQL/Algebra.lean (raw entry point) #607 🟡 NARROWED (2026-08-26, correction note 32 updated) — closed for the query path, open for the raw evalBgp entry point and for the F* tree, whose substitute_existentials still leaves EXISTS-body blank nodes as constants —
NO MULTIPLICITY CLAIM, AND NONE AVAILABLE DOWNSTREAM — Answers is a Prop and the evaluator side is membership. SPARQL/AlgebraSpec.lean keeps the §18.5 set layer and the cardinality layer apart, and the F* bag-refinement proof is not ported, so no downstream theorem ties evalBgp's bag to the specification's — design doc §5.4 ⬜ OPEN (recorded boundary, unchanged from §5.4) —
MEMBERSHIP ON THE NOSE NOT CHARACTERISED — μ ∈ evalBgp b g fixes a binding-list ORDER (the evaluator conses subject → predicate → object, left to right) and tryBindTerm's already-bound arm keeps the FIRST term bound to a variable, comparing only by Term.eqb. Both are invisible to any semantic condition, so bgp_eval_complete concludes AGREEMENT up to Term.eqb, not membership Unified/SparqlAdequacy.lean module header design doc §4.6 ⬜ OPEN (recorded boundary, correction note 27) —
EVALUATOR COMPLETENESS ONLY ON TRIPLE-TERM-FREE PATTERNS — tryBindTerm's .tripleTerm arm recurses through sub-positions with intermediate mappings, which needs the whole *_complete family one level down. BgpTtFree excludes it. (The MODEL side needs the same guard for the independent reason that the term model gives every triple term one quarantine constant) Unified/SparqlAdequacy.lean — ⬜ OPEN (recorded boundary) —
REGIME COMPLETENESS AGAINST THE RUNNING EVALUATOR NOT PROVED — regime_rhoDf_answers_closure_iff is answer-preservation of the MATERIALISATION, not "the engine returns every ρdf answer". The latter needs the closure to be SATURATED, which is the rhoDfClosedCheck hypothesis the stage 2 decided corollary carries; x-rdfsplus has no closure-soundness theorem at all (stage 3 landed the RDFS-Plus Datalog tier at demo-instance strength only) — design doc §4.6 ⬜ OPEN (recorded gap, correction note 30) —
ENGINE REGIME DISPATCH IS NARROWER THAN THE REGIME TABLE — RDFS.entailmentClosureForQueryExt recognises x-rdfscore and x-rdfsplus and routes EVERY other string, "RDFS" and "OWL-RL" included, to OWL.RL.closure. regime_sound_rdfs is about RDF.Regime.closure .rdfs, which is a DIFFERENT entry point. Splitting the dispatch is engine work, not registry work RDFS/RegimeDispatch.lean module header — ⬜ OPEN (recorded divergence, correction note 29) —
String.toLower IS KERNEL-OPAQUE — neither decide nor rfl discharges RDF.langTagEq "EN" "en" = true, so termEqSchema_nontrivial carries the language-tag fact as a hypothesis and the concrete instance is pinned by #guard instead. Not a gap in the theory; a note for the next session that reaches for decide on a string-normalising function Unified/SparqlAdequacy.lean — ⬜ RECORDED (2026-08-26) —

Issue #609 items 1 and 2 (landed 2026-08-26): the host logic's own executable stack against CL/Semantics.lean — CL/FiniteSatTheorems.lean (the satisfaction agreement) and CL/ClifAdequacy.lean (what a CLIF reader-adequacy claim can be here). Design-doc correction notes 35 and 36. Axiom audit on every row below: propext, Classical.choice, Quot.sound only (in-source #print axioms, 16 declarations).

Theorem Module Native anchor Status Fragment / hypotheses
satFin_eq — sat fi v s = true ↔ CL.Sat fi.toInterp v.ind v.seq s; satisfiesFin_eq the same at sentence level. The satisfaction half of the pair whose term half is denotTermFin_eq / denotSeqFin_eq. One induction on the fuel argument carries all five functions of the satisfaction group; the Sentence.size bound CL/FiniteSat.lean's header argued informally is the induction's side condition CL/FiniteSatTheorems.lean CL.Sat / CL.Satisfies (CL/Semantics.lean), sat (CL/FiniteSat.lean) ✅ PROVED (2026-08-26) — FULL iff THREE, each named in the statement: [LawfulBEq α]; hdom : ∀ x : α, x ∈ fi.domain; hns : noSeqQuant s = true. Free sequence markers are NOT excluded
domain_hypothesis_necessary / seqQuant_hypothesis_necessary / lawfulBEq_hypothesis_necessary — each hypothesis shown NOT removable. FiniteInterp.toInterp reads neither domain nor maxSeq, so partialFin/witFin and seqFin0/seqFin1 are the SAME Interp while the checker decides the same sentence differently over them; the BEq row uses a two-element domain type whose == is constantly true CL/FiniteSatTheorems.lean — ✅ PROVED (2026-08-26) — separating pairs concrete witnesses; the BEq row is stated at the sat/Sat level because the unlawful instance changes toInterp
sat_atom_iff / sat_eq_iff / sat_conj_iff / sat_disj_iff / sat_neg_iff / sat_impl_iff / sat_iff_iff / sat_all_iff / sat_ex_iff / satAll_* / satAny_* / satForall_* / satExists_* — one rw [Sat]-proved clause lemma per constructor over an ARBITRARY Interp. Needed because CL.Sat is well-founded-compiled and simp only [Sat] cannot fire at fi.toInterp: the valuation type-checks against String → i.dom only at DEFAULT transparency, simp matches at implicit CL/FiniteSatTheorems.lean CL/Semantics.lean ✅ PROVED (2026-08-26) — reusable, unconditional —
iProp_valuation_independent / denotTermFin_that_constant — the FOURTH condition CL/FiniteSat.lean's header named (valuation-independent that-reading) is NOT a hypothesis of satFin_eq and cannot be: toInterp.iProp is a lookup keyed by canonical CLIF text, so both are rfl CL/FiniteSatTheorems.lean CL/FiniteSat.lean header conditions ✅ PROVED (2026-08-26) — condition reclassified —
witFin_not_ikl_coherent — the boundary that condition was really about: the finite reading does NOT meet CL.IklRespectsThat in general, so satFin_eq is agreement with fi.toInterp, not with IKL entailment. Unified/ClBridge.lean's propModel is the coherent interpretation the finite reading is not CL/FiniteSatTheorems.lean CL.IklRespectsThat (CL/Semantics.lean) ✅ PROVED (2026-08-26) — refutation witness concrete witFin
witFin + wit_sat_boyBill / wit_sat_ex / wit_sat_neg / wit_sat_restricted / wit_not_sat_boySue / wit_not_sat_allBoy — non-vacuity: CL.Examples' tiny over a FINITE domain type, so hdom is decide-dischargeable; the four tiny_sat_* shapes come back through satisfiesFin_eq as Satisfies theorems and two shapes come back as refutations, so the agreement is not compatible with Sat holding of everything CL/FiniteSatTheorems.lean CL/Examples.lean tiny_sat_* ✅ PROVED (2026-08-26) non-vacuity guards
seqItemsToClif_seqmark / bindingToClif_seqmark — the serialiser guards NAME spellings through renderName and writes a SEQUENCE MARKER as the raw "..." ++ m. Both rfl: what is machine-checked is the ABSENCE of a case split CL/ClifAdequacy.lean Sentence.toClif (CL/Clif.lean) ✅ PROVED (2026-08-26) —
markerLexable + sentMarksLexable (with termMarksLexable / seqMarksLexable / bindMarksLexable / sentsMarksLexable) — the fragment the round trip holds on, found by measuring 38 shapes rather than read off the code CL/ClifAdequacy.lean CL/Clif.lean lexer ✅ DEFINED (2026-08-26) —
THE CLIF ROUND TRIP IS FALSE IN GENERAL — .atom (.name "P") [.seqmark "a b"] serialises to (P ...a b) and reads back with TWO argument items. Pinned by #guard through argument COUNTS (Nat has DecidableEq, so the sharpest form uses no comparator). The string-level #guards already in CL/Clif.lean cannot see it: the misparse re-serialises to the SAME text, so stable "(P ...a b)" is true CL/ClifAdequacy.lean CL/Clif.lean ✅ RECORDED (2026-08-26) — counterexample, #guard-pinned markSpaceSentence, markParenSentence
clif_roundTrip — ∀ s, sentMarksLexable s = true → parseClifSentence s.toClif = .ok s — NOT PROVED. Needs the lexer taken apart: lexAcc fuel-monotonicity, a decomposition over ++ with the position counter threaded, the same for parseSExpr over token-list concatenation, then readSentence inverted CL/ClifAdequacy.lean module header ISO/IEC 24707 Annex A ⬜ OPEN (named lemma, correction note 36) —
THE KERNEL CANNOT REDUCE THIS PARSER AT USEFUL SIZES — so INSTANCE-level round-trip theorems (parseClifSentence s.toClif = .ok s by rfl for a concrete s) are unavailable too. Measured 2026-08-26, 16 GB container: (P a) and (P a b) reduce in 4-6 s; (P ...a b) and (forall (x) (P x)) exhaust memory and are killed after 150-200 s. The cliff is in the string primitives the lexer goes through, not the grammar. native_decide is banned. The 38-shape corpus is therefore #guard (COMPILED evaluation): tests, not proofs CL/ClifAdequacy.lean module header — ⬜ RECORDED (2026-08-26) — measurement, plan input —
Sentence.structEq LAWFULNESS NOT PROVED — DecidableEq does not derive for the Term/SeqItem/Binding/Sentence mutual family (checked 2026-08-26: no deriving handler applies), so the corpus tests compare with a hand-written Bool equality. No theorem relates structEq a b = true to a = b; it is test harness. The counterexample rows do not depend on it CL/ClifAdequacy.lean — ⬜ OPEN (recorded boundary) —
ANNEX A HAS NO INDEPENDENT FORMALISATION HERE — a CLIF text means what the abstract sentence it denotes means, which is clause 6 in CL/Semantics.lean. There is no second formal object for the reader to be adequate TO, so "the reader denotes per Annex A" is not statable as an agreement; the round trip against the serialiser is what is available CL/ClifAdequacy.lean module header ISO/IEC 24707 Annex A ⬜ RECORDED (2026-08-26) — scope statement —

IKL normalization (Hayes 2009) — Lean tree#

Issue #580, landed 2026-08-26. Source: Pat Hayes, A Satisfiability-Preserving Reduction of IKL to Common Logic, IHMC 2009, https://jfsowa.com/ikl/PHnsfs09.pdf. Design note: docs/designissues/2026-08-26-ikl-normalization.md. Axiom audit on every row below: propext, Classical.choice, Quot.sound only (in-source #print axioms, 18 declarations).

Theorem Module Native anchor Status Fragment / hypotheses
normTerm / normSeq / normBinds / normSent / normSents — the transformation. STRUCTURALLY RECURSIVE: every recursive call is on a strict subterm (the body of a that is a subsentence), so termination is kernel-checked with no partial, no termination_by and no decreasing_by. The paper gives the algorithm as a loop and says only that "an obvious inductive argument shows that this process of normalization must terminate with a CL text" CL/Normalize.lean Hayes 2009 pseudocode, section "Multiple proposition names" ✅ DEFINED (2026-08-26) — total —
thatWeight_that — the measure the LOOP form needs, and why the obvious one fails. Counting proposition names does not decrease: eliminating an outermost that moves the names nested inside it into a tail. Sentence.thatWeight weights each occurrence by two raised to its that-nesting depth (w (that S) = 1 + 2 * w S), and a loop step drops the text's weight by at least 1 + w S CL/Normalize.lean Hayes 2009, "an obvious inductive argument" ✅ PROVED (2026-08-26) — strict drop per step stated about the loop step; the shipped traversal does not use it
normalizeFrom_pureCL — the OTHER half of "terminates with a CL text": head and every tail satisfy Sentence.isPureCL, so no proposition name survives anywhere in the output. Two mutual inductions over the traversal CL/Normalize.lean Sentence.isPureCL (CL/Syntax.lean) ✅ PROVED (2026-08-26) — every input, unconditional NONE — holds outside the no-intrusion fragment too
The four paradox pins — That Nothing Is True, the Liar, Kripke's semantic paradox and the Knower, each matching the paper's printed output verbatim with the counter started where the paper's gensym had reached. The Knower's ITERATION COUNT is pinned at 2 (clifThatCount == some 2, two tail sentences), the paper's only example needing more than one iteration. Kripke's pin is what fixes intrusion as enclosing-bound INTERSECT free-in-body CL/Normalize.lean Hayes 2009, "Normalizing the paradoxes" ✅ PINNED (2026-08-26) — #guard, so lake build is the test run —
pSat + iklProp + sat_iklProp + iklProp_respects + exists_ikl_interp — a coherent IKL interpretation, in CL/. IklRespectsThat had no model behind it in the CL tree, so every theorem quantified over coherent interpretations was empty. pSat breaks the circularity between CL.Sat and Interp.iProp by resolving that through recursion into the body. Universe Prop, rel x args := x, fn x args := x CL/IklModels.lean CL.IklRespectsThat (CL/Semantics.lean) ✅ PROVED (2026-08-26) — satisfiability witness none. DUPLICATES Unified/ClBridge.lean's propModel (same day, more general in iN iS R F); the dependency runs Unified → CL so the copy could not be reused — resolution recorded in the design note §8
IklPropLocal + pSat_coincide (with pDenot_coincide / pSatAll_coincide / pSatAny_coincide / pSatFA_coincide / pSatEX_coincide) + iklProp_local — the LOCALITY condition: the proposition a sentence expresses depends only on what its free names and free sequence markers denote. Hayes's construction turns on it, because the fresh name gets ONE denotation while the occurrence it replaces sits under quantifiers that change the valuation at names the body does not use CL/IklModels.lean Interp.iProp (CL/Semantics.lean) ✅ PROVED (2026-08-26) — condition stated, and proved for iklProp —
normT_head / normSeq_head / normS_head / normSs_head / normSsAny_head / normFA_head / normEX_head — the head clause: the transformed sentence, read under the constructed valuation, has the truth value the original has under the original valuation. Uses LOCALITY only, not coherence CL/NormalizeSemantics.lean CL.Sat (CL/Semantics.lean) ✅ PROVED (2026-08-26) — full iff no-intrusion fragment; no name from the fresh-name space; the allocation record known to the valuation
normT_tails / normSeq_tails / normBinds_tails / normS_tails / normSs_tails — the tail clause: every emitted tail sentence holds under the constructed valuation. Uses COHERENCE, and NO premise that the original sentence is satisfied — which is what makes the conjecture below follow CL/NormalizeSemantics.lean CL.IklRespectsThat (CL/Semantics.lean) ✅ PROVED (2026-08-26) same three side conditions
normalize_preserves + ikl_sat_to_cl_sat — Hayes's claim, forward half: "any IKL interpretation I of PHI defines a CL interpretation J of S(PHI) which satisfies the tail ... such that in every case, J assigns the same truthvalue to the head as I does to PHI". kapOf is J: the original name valuation with each allocated fresh name pointed at the proposition its that-body denotes CL/NormalizeSemantics.lean Hayes 2009, "Eliminating Proposition names" ✅ PROVED (2026-08-26) — forward direction, full iff on the head three side conditions, all decidable
tails_satisfiable — HAYES'S 2009 OPEN CONJECTURE, for the no-intrusion fragment. The paper: "We have not found any such example, and believe that it is impossible, but do not at the time of writing have a conclusive proof." The tail set of a normalized IKL sentence is ALWAYS CL-satisfiable, whether or not the sentence is. The proof is that the tail induction never uses the sentence's truth, so a model of the tails is built from any coherent local IKL interpretation, and iklProp is one CL/NormalizeSemantics.lean Hayes 2009, "IKL Satisfiability and Entailment" ✅ PROVED (2026-08-26) — no-intrusion fragment three side conditions; NOT proved for the intrusion case (design note §7.1)
tnit_cl_unsat / liar_cl_unsat / kripke_cl_unsat + tnit_not_ikl_sat / liar_not_ikl_sat / kripke_not_ikl_sat — three of the four normalized paradoxes are CL-UNSATISFIABLE, so by ikl_sat_to_cl_sat none of the three sentences has a coherent IKL model CL/NormalizeSemantics.lean Hayes 2009, "Normalizing the paradoxes" ✅ PROVED (2026-08-26) — refutations concrete sentences; side conditions by decide
knower_cl_sat + knower_two_iterations + tnit_tails_sat / liar_tails_sat / kripke_tails_sat / knower_tails_sat — non-vacuity. The Knower's fourth sentence taken alone IS satisfiable and a model is exhibited; and against each of the three refutations sits a model of the TAIL SET alone. For the Liar the whole text is inconsistent while its tail set is not, so the conjecture is not being confirmed by a theorem that would confirm anything CL/NormalizeSemantics.lean CL/IklModels.lean iklProp ✅ PROVED (2026-08-26) — non-vacuity guards —
THE INTRUSION CASE IS NOT COVERED — with a non-empty intrusion list the replacement term is (K U1 ... Un) and the model must give K a non-trivial FUNCTIONAL extension, one proposition per argument tuple. iklProp's Prop universe collapses them (fn x args := x). A universe with a surjection onto List D -> Prop is impossible by cardinality, so this needs a universe built for the finitely many extensions the tails require. Both inductions already carry the intrusion lists CL/NormalizeSemantics.lean module header design note §7.1 ⬜ OPEN (recorded boundary with its obstruction) —
THE CONVERSE DIRECTION IS NOT PROVED — from a CL model of head-plus-tails back to an IKL model. Two obstructions: coherence forces an individual with empty zero-ary extension and one without, which a CL model need not have; and a that-term can sit in an EQUATION (the Liar does), so the converse iProp must return the individual the fresh name denotes, not one with the same truth value — it has to be keyed by the sentence CL/NormalizeSemantics.lean module header design note §7.2 ⬜ OPEN (recorded boundary with its obstruction) —
propName INJECTIVITY NOT PROVED — key-distinctness of the allocation list is what needs it, and it reduces to injectivity of Nat.repr, absent from this toolchain's core library (exact? finds nothing for toString a = toString b → a = b nor for (toString a).toNat? = some a). It stays a hypothesis of the general theorems; the four paradox instances discharge it by decide and carry no such hypothesis CL/NormalizeSemantics.lean module header design note §7.3 ⬜ OPEN (recorded gap) —
NO SKOLEMIZATION — the paper Skolemizes first so that every intrusion is universal, and re-Skolemizes each tail. Not done here, and not needed: the tail holds in the constructed model whichever quantifier bound the intruding name, because the functional extension given to the fresh name is TOTAL. Visible on the paper's second quantifier-intrusion example, where Hayes prints (prop1 x) after Skolemizing and this tree prints (prop1 x y); both are pinned CL/Normalize.lean module header Hayes 2009, "Quantifier intrusions" ✅ RECORDED (2026-08-26) — divergence, both forms pinned —

Stage 7 (2026-08-26): no new theorems. The account of what stages 1-6 proved — every gate theorem quoted as landed with its exact strength, the named gaps, the defects the proof attempts found in shipping code, and the list of claims NOT made — is docs/designissues/2026-08-26-lbase-account.md. Public version, with the ρdf closure, Finding C-1's separating pair and an OWL 2 RL row computed live beside the theorems that cover them: hub post 43, docs/web/hub/43-one-model-theory-under-all-of-it.md (cells pinned by tests/hub/post43_test.mjs).

Planner soundness (Lean 4, 2026-09-06)#

A skipped manifest entry cannot contribute a row. Design record: designissues/2026-09-06-planner-soundness-theorem.md; tracking #658 and #614.

Theorem Module Native anchor Status Fragment / hypotheses
igSearch_ofGraph_filter — a bound whose every match survives a Boolean keep reads the same rows, in the same order, from the restricted graph as from the whole one. Reduces the hash index to List.filter through OWL.RL.Index.Wf SPARQL/DatasetRestriction.lean RDF.igSearch (RDF/StoreCapabilities.lean) ✅ PROVED (2026-09-06) — list equality BoundKept keep b
evalBgpBackend_restrict — the BGP evaluator including the planner: equal estimates give the same plan in the same order SPARQL/DatasetRestriction.lean StorePlan.evalBgpBackend ✅ PROVED (2026-09-06) — list equality every triple pattern's bounds kept
evalPatternBackend_restrict — the pattern induction. Covers BIND(e AS ?v) for an Expr.backendLocal e since 2026-09-06 (second landing), through the backend-native .bind arm added to evalPatternBackend for that admission SPARQL/DatasetRestriction.lean StoreDataset.evalPatternBackend ✅ PROVED (2026-09-06) — list equality plannerFragment, PatternKept, graph names readable, GRAPH ?v only when no graph-name selection was made
evalPatternBackend_nil — a graphFree fragment pattern over the empty active graph answers nothing, or keep holds of every triple. The side condition the two empty leaves and the BIND arm need SPARQL/DatasetRestriction.lean StoreDataset.evalPatternBackend ✅ PROVED (2026-09-06) plannerFragment, graphFree, PatternKept
PatternKept_mono, patternKept_of_quadPredicates, patternKept_of_queryPredicates — the predicate collector establishes the evaluator theorem's PatternKept hypothesis, BIND arm included Storage/PlannerSoundness.lean ShardManifest.quadNativeConstantPredicates? ✅ PROVED (2026-09-06) collector returns some P
runSelectQueryBackendDataset_restrict / runAskQueryBackendDataset_restrict — the two query entry points, covering the four fast paths (COUNT(*), the two streaming GROUP BYs, the LIMIT push-down) SPARQL/DatasetRestriction.lean StoreDataset.runSelectQueryBackendDataset, runAskQueryBackendDataset ✅ PROVED (2026-09-06) same, over q.pattern.rewriteBnodes; a FIXED env
datasetRestricted_restrictDataset — the canonical restriction satisfies the relation Storage/PlannerSoundness.lean DatasetRestriction.DatasetRestricted ✅ PROVED (2026-09-06) distinct named-graph keys, no empty named graph
plannerSoundnessSelect / plannerSoundnessAsk — the composed statement of the design record's section 2 Storage/PlannerSoundness.lean ShardManifest.quadEntriesForQueryWithKeys ✅ PROVED (2026-09-06) — modulo the dataset obligation below restrictDataset keep (D S) = restrictDataset keep (D E)
A GRAPH inside a GRAPH was UNSOUND to select on — section 18.6 gives GRAPH <n> { P } no solutions when the dataset does not name n, whatever P is Storage/ShardManifest.lean (plannerFragmentQuery) SPARQL 1.1 §18.6 ✅ REPAIRED (2026-09-06) — collectors refuse it, #guard pins the refusal —
A language-tagged or rdf:XMLLiteral constant object was UNSOUND to zone-test on — Term.eqb folds language-tag case and canonicalises XML, a zone bound is the version-2 wire key Storage/ShardManifest.lean (constantObjectOf) RDF.exactObjectIndexKeySafe ✅ REPAIRED (2026-09-06) —
restrictDataset keep (D S) = restrictDataset keep (D E) — the storage obligation. Needs an Index.Wf-style characterisation of datasetOfQuads (a foldl over Std.HashMap) plus the entry-level facts under the activation invariants Storage/QuadDataset.lean, Storage/PlannerSoundness.lean Storage.QuadDataset.datasetOfQuads ⬜ OPEN (2026-09-06) — recorded with its route —
The DELEGATING arms of evalPatternBackend (FILTER / OPTIONAL / BIND with an expression that is not Expr.backendLocal, property paths, sub-SELECT, VALUES, SERVICE, LATERAL) — they materialise the dataset and run the algebra evaluator, a second evaluator the induction does not reach SPARQL/DatasetRestriction.lean module header QueryPattern.lowerWith / GraphPattern.evalIn ⬜ OPEN (2026-09-06) — the collectors refuse them, so the planner reads more —

GeoSPARQL bounding-box index (Lean 4, 2026-09-07)#

The GBI1 index is a CANDIDATE FILTER: a box can exclude a row, never confirm one. Soundness is the single direction "a term the exact predicate accepts is a candidate". Design records: designissues/2026-09-05-geometry-bounding-box-index.md and the Geo section of designissues/2026-09-07-lean-rif-rml-csvw-geo-gap.md.

Theorem Module Native anchor Status Fragment / hypotheses
mem_candidatesSpec — a dictionary term the exact GeoSPARQL predicate accepts is in the candidate list Storage/GeoBBoxIndex.lean GeoBBoxIndex.candidatesSpec ✅ PROVED op ≠ sfDisjoint; query geometry in the fragment
exists_common_point — two fragment geometries a served predicate accepts have a point both boxes contain Storage/GeoBBoxIndex.lean GeoOp.fn (Geo.sfEquals and siblings) ✅ PROVED (2026-09-07, polygon-pair sfIntersects case closed) op ≠ sfDisjoint; point or closed-ring polygon on both sides
polygonsIntersect_common_point — the polygon pair, all three disjuncts of Geo.polygonsIntersect Geo/BBoxSound.lean Geo.polygonsIntersect ✅ PROVED (2026-09-07) both boxes cover every ring; both exterior rings closed
segmentsIntersect_common_point — any two intersecting segments have a point in both covering boxes Geo/BBoxSound.lean Geo.segmentsIntersect ✅ PROVED (2026-09-07) each box contains its segment's two endpoints
segmentsCross_intervals — the PROPER-crossing branch: four orientation signs with no inSegBBox conjunct still put the two coordinate intervals in overlap, on both axes Geo/BBoxSound.lean Geo.orientSign ✅ PROVED (2026-09-07) — closes the obligation the GeoBBoxIndex header named open orientSign a b c ≠ orientSign a b d and orientSign c d a ≠ orientSign c d b
int_cross_axis / int_cross_axis_gen — the parametric argument in integer arithmetic. D times the crossing coordinate has one polynomial form per segment, so no division and no rational point Geo/BBoxSound.lean — (arithmetic) ✅ PROVED (2026-09-07) straddle signs; D = v - u ≠ 0
polygonClass_ne_exterior_contains, ringClass_interior_contains — the ray cast puts a non-exterior point inside a covering box Geo/BBoxSound.lean Geo.polygonClass ✅ PROVED (earlier) closed exterior ring; simple ring
geof:sfDisjoint is NOT served by the index — it accepts exactly the rows a box can exclude, so a box test inverts there Storage/GeoBBoxIndex.lean (candidatesSpec) — ✅ REFUSED BY CONSTRUCTION candidatesSpec returns none; the caller scans
LineStrings, Multi*, GeometryCollection and open-ring polygons carry NO box — they are opaque and always candidates Storage/GeoBBoxIndex.lean (fragmentBox) — ⬜ OUT OF THE PROVED FRAGMENT the caller re-evaluates, so no answer is lost

Every theorem in this section reports propext, Classical.choice, Quot.sound under #print axioms. Probe: lake -d formal/lean4 exe l4geo — 37 pass, 0 fail (out of 37), the same 37 assertions as the F* geosparql-v0 pin file.

OWL 2 comprehension and existential witnesses (Lean 4, 2026-09-07)#

The PositiveEntailmentTest-only layer of L4Factoidal/OWL/Comprehension.lean and its specification in ComprehensionTheorems.lean. Design record: designissues/2026-09-07-lean-owl-corpus-gap.md.

CompStar is kept apart from RLRules.Derives for the reason RLRules.ExtClash is kept apart from RLRules.Clash: these rows mint blank nodes and rest on OWL 2 RDF-Based Semantics § 8 comprehension conditions that the RL profile does not assume, so putting them in Derives would widen what the RL profile claims and break RLSemantics.rl_derives_holds, whose conclusion extends the assignment at two reserved label families only.

Theorem Module Engine anchor Status Fragment / hypotheses
comprehensionLayer_sound — every triple the layer returns is a premise or a CompStar consequence of premises OWL/ComprehensionTheorems.lean Comp.comprehensionLayer ✅ PROVED none
layerStage1_sound / layerStage2_sound / layerStage3_sound — one per stratification stage same Comp.layerStage1–3 ✅ PROVED none
foldl_addAll_sound — a stage preserves "every triple is CompStar" same RL.addAll ✅ PROVED the row's own soundness
compUni1For_sound (§ 8.2 + 8.1), compTrpChainFor_sound (§ 5.10 + 8.1), compAdfCliqueFor_sound (§ 5.7 + 8.1), pdwDiffFor_sound (§ 5.10), prpKeyThingFor_sound (§ 5.2 + Structural Specification 9.5), svfThingMatFor_sound (§ 8.4), svfThingWitFor_sound (§ 5.4), hasSelfSynthFor_sound (§ 5.4 + 8.4) same the eight row functions ✅ PROVED (all eight) none beyond a CompStar driving triple
pairwiseDifferent_sound — the owl:AllDifferent row's executable side condition unpacks into the graph facts it names same Comp.pairwiseDifferent ✅ PROVED none
Model-theoretic soundness of CompStar — under an interpretation meeting the § 8 comprehension conditions and an assignment extended at the six reserved label families, CompStar base t holds whenever base does — — 🔴 NOT ATTEMPTED needs a compExtend decoding six label families (RLSemantics.rlExtend does two) and one condition per row

The last row is the standing gap. Until it lands each row's semantic licence is the specification citation in Comprehension.lean's header table, which is the standing of the F* engine's own comprehension layer (OWL.Closure.fsti § 20b). It is recorded here rather than left to inference, per anti-pattern 29.

10. JOSE, DPoP and Solid-OIDC over HACL* — Lean tree#

Landed 2026-09-06. Design record: designissues/2026-09-06-jose-dpop-over-hacl.md.

The trust split, stated once for the whole section. HACL* is trusted for the arithmetic — SHA-256, Ed25519, ECDSA over NIST P-256, and modular exponentiation over a 2048-, 3072- or 4096-bit modulus — through four opaque declarations realised by three length-checking shims over vendored, unmodified extracted C (third_party/hacl/PROVENANCE.md). Lean decides everything else: the byte encodings, the algorithm allowlist, the key-type match, the claims policy and the DPoP binding.

No theorem below depends on an opaque. The three signature primitives arrive in a JOSE.Verifiers record and the RSA public operation as a Pkcs1.PublicOp parameter, so every statement is universally quantified over them and holds whatever HACL* returns. #print axioms on all 49 reports only propext, Classical.choice, Quot.sound. What HACL* actually computes is MEASURED, not assumed: lake exe l4jose-probe (1077 pass, 0 fail, 3 acceptable, out of 1080) and node tests/jose/webcrypto-differential.mjs (ES256 400 pass, 0 fail out of 400; RS256 400 pass, 0 fail out of 400).

statement module source status
decode (encode x) = some x for every octet string JOSE/Base64Url.lean decode_encode RFC 7515 §2 ✅ proved
every accepted base64url string re-encodes to itself, so an octet string has one spelling JOSE/Base64Url.lean encode_decode RFC 7515 §2 ✅ proved
distinct octets give distinct strings JOSE/Base64Url.lean encodeChars_injective RFC 7515 §2 ✅ proved
every accepted character is in the RFC 4648 §5 alphabet JOSE/Base64Url.lean digitsOf_alphabet RFC 4648 §5 ✅ proved
a string containing = never decodes JOSE/Base64Url.lean decode_refuses_padding RFC 7515 §2 ✅ proved
a string containing any non-alphabet character never decodes JOSE/Base64Url.lean decode_refuses_non_alphabet RFC 7515 §2 ✅ proved
the encoded message is exactly k octets JOSE/Pkcs1.lean emsaEncode_length RFC 8017 §9.2 ✅ proved
it begins 00 01 JOSE/Pkcs1.lean emsaEncode_prefix RFC 8017 §9.2 ✅ proved
the padding is k-54 octets of FF at offset 2 JOSE/Pkcs1.lean emsaEncode_padding RFC 8017 §9.2 ✅ proved
the padding is at least eight octets JOSE/Pkcs1.lean emsaEncode_padding_min RFC 8017 §9.2 step 3 ✅ proved
the tail is 00 || DigestInfo(SHA-256) || digest, with no room after it JOSE/Pkcs1.lean emsaEncode_tail RFC 8017 §9.2 note 1 ✅ proved
one digest, one encoded message JOSE/Pkcs1.lean emsaEncode_injective RFC 8017 §9.2 ✅ proved
RS256 accepts IFF the recovered block equals the generated template — the Bleichenbacher 2006 class excluded by construction, stated in both directions so no other accepting path can be added JOSE/Pkcs1.lean rs256Verify_iff_template RFC 8017 §8.2.2 note 2 ✅ proved
an accepted signature recovered exactly n.size octets JOSE/Pkcs1.lean rs256Verify_recovers_full_length RFC 8017 §8.2.2 ✅ proved
a refused public operation is a rejection, never a degradation to acceptance JOSE/Pkcs1.lean rs256Verify_refusal_rejects RFC 8017 §5.2.2 ✅ proved
a parsed key's type is the one its kty named JOSE/Jwk.lean parseJwk_kty RFC 7517 §4.1 ✅ proved
every EC key is 32+32 octets, so the raw key is always 64 JOSE/Jwk.lean parseJwk_ec_sizes, ecPublicRaw_size RFC 7518 §6.2.1 ✅ proved
every RSA key has a modulus the public operation accepts (2048, 3072 or 4096 bits) JOSE/Jwk.lean parseJwk_rsa_size RFC 7518 §3.3 ✅ proved
a JWK carrying private key material is never accepted as a public key JOSE/Jwk.lean parsePublicJwk_no_private RFC 9449 §4.2 ✅ proved
the public parse only refuses; it never loosens JOSE/Jwk.lean parsePublicJwk_le_parseJwk RFC 9449 §4.2 ✅ proved
a refusal decided before the key is the outcome JOSE/Jws.lean verify_refuses_at_pre_key_stage RFC 7515 §5.2 ✅ proved
a token refused before the key stage gives the same outcome for EVERY key and EVERY verifier — the precise sense of "never reaches the EC, RSA or Ed25519 verifier" JOSE/Jws.lean verify_independent_of_key_before_key_stage RFC 7515 §5.2 ✅ proved
an alg outside the allowlist stops the token before the key JOSE/Jws.lean alg_not_allowed_stops_before_key RFC 7518 §3.1 ✅ proved
no HMAC algorithm is in the allowlist — algorithm confusion JOSE/Jws.lean hmacFamily_not_allowed RFC 7518 §3.2 ✅ proved
alg: none is not in the allowlist, in every spelling JOSE/Jws.lean alg_none_not_allowed RFC 7518 §3.6 ✅ proved
the other RFC 7518 algorithms this project cannot check are refused, not silently accepted JOSE/Jws.lean unsupported_algs_not_allowed RFC 7518 §3.1 ✅ proved
the allowlist maps each accepted name to the algorithm whose name it is JOSE/Jws.lean algOfString_roundTrip RFC 7518 §3.1 ✅ proved
a verification only happens when the key type matches the algorithm JOSE/Jws.lean verifyWith_kty_matches, verified_kty_matches RFC 7518 §3.3, §3.4, RFC 8037 §3.1 ✅ proved
a crit header is a rejection JOSE/Jws.lean crit_refused RFC 7515 §4.1.11 ✅ proved
a token that is not three parts reaches nothing JOSE/Jws.lean not_three_parts_refused RFC 7515 §3.1 ✅ proved
the claims diagnosis cannot accept, so validate has one accepting path JOSE/Jwt.lean diagnose_ne_ok — ✅ proved
the whole claims-acceptance condition, in both directions JOSE/Jwt.lean validate_ok_iff RFC 7519 §4.1 ✅ proved
an accepted token was minted by the pinned issuer JOSE/Jwt.lean validate_ok_issuer_pinned RFC 7519 §4.1.1 ✅ proved
an accepted token names this verifier in its audience JOSE/Jwt.lean validate_ok_audience_pinned RFC 7519 §4.1.3 ✅ proved
an accepted token has an expiry and has not reached it JOSE/Jwt.lean validate_ok_unexpired RFC 7519 §4.1.4 ✅ proved
a token with no expiry is never accepted JOSE/Jwt.lean validate_no_exp_refused RFC 7519 §4.1.4 ✅ proved
a token from another issuer is never accepted JOSE/Jwt.lean validate_wrong_issuer_refused RFC 7519 §4.1.1 ✅ proved
moving the clock past the expiry turns an accepted token into a refused one JOSE/Jwt.lean validate_expires RFC 7519 §4.1.4 ✅ proved
the htu comparison is exactly the comparison of the query- and fragment-stripped forms JOSE/DPoP.lean htuMatches_iff, strip_takeWhile RFC 9449 §4.3 item 9 ✅ proved
a query is ignored; a fragment is ignored JOSE/DPoP.lean htu_query_ignored, htu_fragment_ignored, with takeWhile_all, takeWhile_append_neg RFC 9449 §4.3 item 9 ✅ proved
a difference in scheme, authority or path is refused JOSE/DPoP.lean htu_refuses_difference RFC 9449 §4.3 item 9 ✅ proved
the proof diagnosis cannot accept, so validateProof has one accepting path JOSE/DPoP.lean diagnoseProof_ne_ok, validateProof_ok_iff, proofAccepts_parts RFC 9449 §4.3 ✅ proved
an accepted proof's key has the thumbprint the access token's cnf.jkt names JOSE/DPoP.lean proof_ok_thumbprint_bound RFC 9449 §6.1 ✅ proved
an accepted proof carries the ath of the token presented with it JOSE/DPoP.lean proof_ok_ath_bound RFC 9449 §4.3 item 10 ✅ proved
a token bound to no key is refused, not treated as a bearer token JOSE/DPoP.lean proof_unbound_token_refused RFC 9449 §6.1 ✅ proved
a JWS refusal stops a proof JOSE/DPoP.lean proof_jws_refusal RFC 9449 §4.3 item 2 ✅ proved
Solid-OIDC authentication is off by default, and off changes nothing Solid/Server/Auth.lean authenticate_disabled, requesterOf_unchanged_when_auth_off — ✅ proved
a request with no Authorization is anonymous, never authenticated Solid/Server/Auth.lean authenticate_no_credential Solid Protocol §10.1 ✅ proved
a Bearer credential is refused, not accepted — a key-bound token presented without its proof Solid/Server/Auth.lean authenticate_bearer_refused RFC 9449 §7.1 ✅ proved
a DPoP credential with no proof is refused Solid/Server/Auth.lean authenticate_missing_proof RFC 9449 §7.1 ✅ proved
an authenticated request's proof validated for this method and this URI against this token Solid/Server/Auth.lean checkCredential_authenticated_proof_ok RFC 9449 §4.3 ✅ proved
an authenticated request's token passed the claims policy Solid/Server/Auth.lean checkCredential_authenticated_claims_ok RFC 7519 §4.1 ✅ proved
an authenticated request's token was bound to a key Solid/Server/Auth.lean checkCredential_authenticated_bound RFC 7800 §3.1 ✅ proved
an authenticated request's WebID came from the token; it is never invented Solid/Server/Auth.lean checkCredential_authenticated_webid Solid-OIDC §6 ✅ proved
a key that does not verify the token never authenticates Solid/Server/Auth.lean checkCredential_no_key_refused Solid Protocol §10.1 ✅ proved
no configured identity-provider key refuses every credential; it does not fall open Solid/Server/Auth.lean checkCredential_no_keys Solid Protocol §10.1 ✅ proved

Trust surface added by this section#

what who guarantees it how it is checked here
SHA-256, Ed25519 (FIPS 180-4, RFC 8032) HACL*, Project Everest lake exe l4vc-probe (119 pass, 0 fail)
ECDSA over NIST P-256 with SHA-256 HACL* Hacl_P256_ecdsa_verif_p256_sha2 Wycheproof ecdsa_secp256r1_sha256_p1363 (262 pass, 0 fail), the WebCrypto differential (400 pass, 0 fail)
s^e mod n over 2048/3072/4096-bit moduli HACL* Hacl_Bignum64_mod_exp_vartime Wycheproof rsa_signature_{2048,3072,4096}_sha256 (773 pass, 0 fail, 3 acceptable), the WebCrypto differential (400 pass, 0 fail)
ffi/hacl_p256.c, ffi/hacl_rsa.c this repository, reviewable by eye length checks, one pointer offset and one memcpy; no arithmetic beyond a big-endian bit count over the PUBLIC exponent
the Lean C FFI convention Lean 4 —

Not proved, and where it is stated instead#

gap why where it is recorded
ECDSA signature malleability (s and n-s both verify) ECDSA is malleable by design and JWS does not require a low-s form, so refusing it would be non-conformant Crypto/P256Native.lean module header
that n is a genuine RSA modulus key trust is the issuer-pinning layer's, not the primitive's Crypto/RsaNative.lean module header
that HACL* computes what its specification says it is opaque; no theorem depends on it measured by l4jose-probe and the WebCrypto differential
replay detection the store is the host's; freshness is a String → Bool parameter JOSE/DPoP.lean ProofPolicy.jtiFresh

The function library — L4Factoidal.Fn (2026-09-07)#

Tracking: #664. Design record: designissues/2026-09-07-function-library.md.

One implementation of the XQuery and XPath Functions and Operators semantics, over the L4Factoidal.XSD value spaces, with RIF-DTB, SPARQL 1.1 §17 and XPath 1.0 §4 as calling front ends. No sorry, no user axiom, no partial, no native_decide; #print axioms over all 27 theorems gives propext, Classical.choice, Quot.sound or fewer.

Theorem Module What it states Status
encodeForUri_rif, encodeForUri_sparql Fn/Theorems.lean RIF's func:encode-for-uri and SPARQL's ENCODE_FOR_URI ARE Fn.String.encodeForUri (F&O §5.4.5) ✅ PROVED — rfl; a reintroduced second copy stops the build
substringBefore_sparql, substringAfter_sparql Fn/Theorems.lean SPARQL STRBEFORE/STRAFTER are F&O §5.4.2/§5.4.3 ✅ PROVED — rfl
substringIndex_xpath Fn/Theorems.lean XPath 1.0's substring index is Fn.String.indexOfSub ✅ PROVED — rfl
langMatches_sparql Fn/Theorems.lean SPARQL langMatches is RFC 4647 §3.3.1 basic filtering ✅ PROVED — rfl
addDec_rif, subDec_rif, mulDec_rif, divDec_rif, listIndex_rif Fn/Theorems.lean RIF-DTB 4.5 and 4.11 call Fn.Numeric and Fn.List ✅ PROVED — rfl
substring_sparql_differs_from_fando Fn/Theorems.lean SPARQL SUBSTR clamps a start below 1 where fn:substring keeps the window rule. Witness ("foobar", 0, 3): "foo" against "fo" ✅ PROVED — a stated difference between two Recommendations, not a defect
substring2_rif_differs_from_fando Fn/Theorems.lean the Approved Builtins_String fixture's 2-argument substring counts from 0; F&O counts from 1. Witness ("foobar", 3): "bar" against "obar" ✅ PROVED
concat_nil Fn/Theorems.lean fn:concat and SPARQL CONCAT are variadic including the empty list; XPath 1.0 concat() needs two arguments, which is a front-end arity rule ✅ PROVED
divide_by_zero_has_no_value Fn/Theorems.lean F&O's FOAR0001 is none in the library; each front end maps it (RIF unknown, SPARQL error, XPath NaN) ✅ PROVED
dateTimeStamp_requires_a_timezone Fn/Theorems.lean XSD 1.1 §3.4.28 as a condition on the VALUE, not a separate seven-property kind ✅ PROVED
years_months_decompose Fn/Duration.lean fn:years-from-duration and fn:months-from-duration decompose the month count exactly: y * 12 + m = months ✅ PROVED — Int.tdiv_mul_add_tmod
sub_self_mantissa, normFuel_zero_mantissa Fn/Duration.lean subtracting an XSD.Dec from itself gives zero, through Dec.norm ✅ PROVED
equal_refl, neg_neg, addYearMonth_subYearMonth Fn/Duration.lean F&O §10.4.1 reflexive; negation an involution; §10.6.1 and §10.6.2 inverse on the month count ✅ PROVED
subtract_self Fn/DateTime.lean F&O §10.8: a value minus itself is a zero duration, timezoned or not ✅ PROVED
withImplicitTz_idem Fn/DateTime.lean the RIF-DTB 4.8 implicit-timezone rule may be applied at every entry point without tracking whether it already was ✅ PROVED
timezoneFrom_isSome, plusDuration_preserves_tz Fn/DateTime.lean §10.5.13 has a value exactly when the value has a timezone; §10.7 carries the timezone through ✅ PROVED
listIndex_lt Fn/List.lean every index RIF-DTB 4.11 accepts is a position of the list, which is why get?/remove?/insertBefore? need no bound check ✅ PROVED
lessThan_asymm, trichotomy Fn/Boolean.lean F&O §9.2's xs:boolean order is total and antisymmetric over the whole two-element value space ✅ PROVED

Six further checks are #guards and NOT theorems, and the reason is recorded at their site: both sides call String.toLower or String.startsWith, which are @[extern], so the kernel cannot reduce them — decide gets stuck and rfl fails. native_decide would close the gap by trusting the compiler inside the kernel and is banned in this repository. They are matchesLanguageRange/langMatchesBasic on de-Latn-DE against de-*-DE (the extended-versus-basic filtering difference), func:numeric-divide by zero, and the two RIF-DTB 3.2 date/dateTime containment directions.