SHACL 1.2 Validation via Apache Jena
Context and Problem Statement
opda validates its ontology and round-trip data with pyshacl (==0.25.0) at SHACL-Core + Advanced-Features level, with inference="rdfs", wired into the BASPI5 round-trip and CI (ADR-0014; the ci-baspi5-roundtrip gate and a 15-exemplar matrix). ODR-0002 pins SHACL 1.2 by name.
The problem: pyshacl does not support SHACL 1.2. It tracks SHACL 1.0 plus the 2017 W3C Recommendation, with no roadmap for 1.2. Any SHACL-1.2 construct is therefore silently skipped (best case) or mis-reported (worst case) — opda has already brushed this edge (ODR-0010 §Rules records the sellersCapacity sh:xone needing advanced=True, and the SHACL-Core-only vacuous-pass risk; handover 2026-06-01 §7). The sibling project (~/source/hm/semantic-modelling) hit the same wall and moved validation to Apache Jena (jena-shacl) — “pyshacl is NOT supported; it silently skips SHACL 1.2 constraints” (its ADR-0147). Crucially, Jena SHACL also implements the SHACL-AF features opda’s ODR-0010/0017 depend on (sh:rule, sh:sparql), so the move gains 1.2 capability without losing AF. The reach is broader than SHACL: Jena riot parses the RDF 1.2 triple-term syntax that rdflib cannot (ODR-0025 §R5), so standardising on Jena removes opda’s last reason to depend on rdflib at all. This ADR decides opda’s Jena-based validation and parsing toolchain and the migration discipline.
Decision Drivers
- ODR-0002 pins SHACL 1.2 — the validator must actually implement it, not silently skip it.
- ODR-0010 names pyshacl
advanced=Trueas the capability floor (sh:xonediscrimination;sh:rule/sh:sparqlatsh:Info/sh:Warning). Any replacement MUST meet or exceed it, demonstrably. - ODR-0025 §R3: SHACL validates triples (the ADR-0035 R1 closure / asserted graph), not via an OWL reasoner; entailment is ADR-0035’s job, kept separate.
- Must not silently regress the existing round-trip/exemplar CI (ADR-0014).
- The validator should align its entailment with ODR-0025 R1 rather than apply broader ad-hoc RDFS (this is what removes the EPCCertificate exposure, ODR-0025 §R7).
- opda standardises on one RDF toolchain; it must not retain an
rdflib/pyshacl dependency that cannot parse RDF 1.2 triple-term syntax and would constrain the data model (ODR-0025 §R5).
Considered Options
- A — Apache Jena
jena-shacl6.1.0 as opda’s SHACL 1.2 validator andriotas its RDF parser;rdflib/pyshacl retired — validation and parsing move to Jena; removal of the pyshacl path is gated on a demonstrated-parity check, after whichrdflibis not retained. - B — Stay on pyshacl — keep pyshacl
0.25.0as the sole validator. - C — TopBraid SHACL / DASH (Java) — a different Java SHACL engine as primary.
- D — Dual-run pyshacl + Jena permanently — both validators forever.
Decision Outcome
Chosen option: A — Apache Jena jena-shacl 6.1.0, with rdflib/pyshacl retired (not retained), because pyshacl cannot validate SHACL 1.2 (a hard ODR-0002 requirement) and opda has already hit its SHACL-AF edges, while Jena implements both SHACL 1.2 and the SHACL-AF features opda relies on — and riot parses the RDF 1.2 triple terms rdflib cannot (ODR-0025 §R5).
The pyshacl/rdflib path is retired, not retained. A parity gate proves Jena reproduces opda’s specific AF results (the sellersCapacity sh:xone non-conformant case; the ODR-0017 sh:rule/sh:sparql sh:Info/sh:Warning rules) across the full 15-exemplar matrix — and once it passes, the pyshacl path is removed, not kept on as a parse-checker. Retaining rdflib would re-introduce the exact dependency that, in the sibling project, forced an RDF-1.2 triple-term downgrade (ODR-0025 §R5); Jena riot is opda’s parser and jena-shacl its validator. The parity gate is the transition mechanism — honouring ODR-0010’s capability-floor discipline (demonstrate the floor before removing the incumbent), not a licence to keep two toolchains.
Consequences
- Good, because opda can finally validate the SHACL 1.2 constraints ODR-0002 commits to — no silent skips or vacuous passes.
- Good, because validation and inference become one coherent Jena-stack story: the validator targets the ADR-0035 R1 closure, aligning entailment with ODR-0025 and dissolving the EPCCertificate exposure (§R7).
- Good, because it gains SHACL-AF parity-or-better (
sh:rule/sh:sparql) plus 1.2 features (sh:ShapeClassdual-typing,sh:targetObjectsOf/sh:targetSubjectsOf). - Bad, because it introduces a JVM/Jena dependency into the validation toolchain (install
jena/jena-shacl6.1.0) and a Java step in CI. - Bad, because until the parity gate passes opda dual-runs (pyshacl + Jena) — a transitional double cost that ends in a single Jena toolchain, not a retained dual-run.
- Good, because standardising the parser on Jena
riot(notrdflib) unblocks RDF 1.2 triple-term syntax for statement-level annotation (ODR-0025 §R5) and removes opda’srdflibdependency; the BASPI5 round-trip harness (ADR-0014) migrates from pyshacl to Jena SHACL. - Neutral, because the sibling’s 3-pass meta/instance SHACL structure is optional for opda — adopt it only if opda splits Layer A (inline
sh:ShapeClass) from Layer B (per-category meta-shapes); opda’s current flat shapes need a singleshacl validatepass.
Confirmation
- Parity gate (precondition to retiring pyshacl validation): for each of the 15 exemplars, Jena and pyshacl agree on
sh:conformsand on the located violation — in particular thesellersCapacitysh:xonenon-conformant case and the ODR-0017Info/Warningrules. - 1.2-feature smoke test: a shape using a 1.2-only feature (e.g.
sh:ShapeClassorsh:targetObjectsOf) validates correctly under Jena and is demonstrably skipped by pyshacl — the guard that proves why the swap is required. - The validator runs against the ADR-0035 R1 closure (or the asserted graph), never ad-hoc full RDFS.
Implementation note (2026-06-01, as built — transition COMPLETE). The parity gate passed 17/17 exemplars (pyshacl ≡ Jena on
sh:conforms) once a missingPREFIX rdf:was added to theShInSemantics_MetaShapeSHACL-SPARQL query (strict Jena rejected it; pyshacl had tolerated it). With the floor demonstrated, pyshacl is removed —jena-shacl6.1.0 is now opda’s sole SHACL engine (opda_gen.jena_shacl), the parity machinery (ci-shacl-parity) is retired, and the pyshacl dependency is dropped. The dual-run window is closed.No Fuseki/Jena container. Apache publishes no Jena 6.x Fuseki Docker image, so — as this ADR already specifies — opda uses the Apache Jena 6.1.0 binary distribution, not a container: the
shaclCLI is resolved fromOPDA_JENA_HOME/PATHor auto-provisioned (downloaded + sha512-verified into.jena/), and CI provisions only a JDK (setup-java@21). The inference runtime (ADR-0035/0037) likewise runs the local Fuseki 6.1.0 binary viaconfig/fuseki-config.ttl, with Docker dropped from the build path entirely.
Pros and Cons of the Options
A — Jena jena-shacl 6.1.0 + riot (rdflib/pyshacl retired)
- Good, because it is the validator that actually implements SHACL 1.2, and it supports SHACL-AF; it shares the Jena stack with ADR-0035 inference.
- Bad, because of the JVM dependency and the transitional dual-run until parity is proven.
B — Stay on pyshacl
- Good, because zero migration; current CI is unchanged.
- Bad, because it cannot validate SHACL 1.2 — it silently skips 1.2 constraints, violating ODR-0002. This is the known wall.
C — TopBraid SHACL / DASH
- Good, because a mature Java SHACL engine with DASH support.
- Bad, because heavier/licensing considerations; the sibling uses TopBraid only as a DASH supplement to Jena, not as primary — no advantage over Jena for opda’s needs.
D — Dual-run permanently
- Good, because defence-in-depth.
- Bad, because permanent double maintenance and divergence risk; pyshacl still cannot see 1.2 constraints, so it contributes false confidence rather than coverage.
More Information
- Realises: ODR-0025 §R3 (validation half) and §R5 (Jena parsing toolchain; full RDF 1.2 adoption; no
rdflibdependency). - Cross-project:
docs/hm-handoff-rdf-1.2-triple-term-jena-fix.md— the sibling’srdflib-driven RDF-1.2 downgrade and how to reverse it. - Capability floor that gates retirement: ODR-0010 §Rules (pyshacl
advanced=True—sh:xone,sh:rule/sh:sparql); handover 2026-06-01 §7. - Validation contract: ODR-0013 (SHACL validation & severity), ODR-0017 (SHACL-AF non-blocking data-quality rules).
- Migrates: ADR-0014 (BASPI5 round-trip — pyshacl
==0.25.0,inference="rdfs",ci-baspi5-roundtrip, the 15-exemplar matrix). - Validates against: ADR-0035 (the materialised R1 closure / asserted graph).
- Prior art (replicated): hm
ADR-0147(Jena SHACL 1.2, 3-pass, “pyshacl does NOT support SHACL 1.2”), hmADR-0095/ADR-0127(jena-mapper validate-before-write), hmscripts/validate-ontology.sh; Apache Jenajena-shacl6.1.0.
Comments
Loading comments…
Sign in to post a comment