Repository navigation
Description
Status (2026-10-13): implemented in linkml#3990 (fork #20): one
sh:SPARQLConstraintper postcondition slot (option 2). Close when it merges.
What
The composed rule translation in gen-shacl (linkml#3990, _compose_rule_sparql) skips every rule whose postconditions constrain more than one slot. gen-json-schema enforces such rules in a single then.
Reproduction (linkml#3990 head c89faac)
# schema.yaml
id: https://example.org/m
name: m
prefixes:
ex: https://example.org/m/
linkml: https://w3id.org/linkml/
default_prefix: ex
imports: [linkml:types]
classes:
Thing:
attributes:
kind: {range: string}
status: {range: string}
level: {range: integer}
rules:
- preconditions:
slot_conditions:
kind: {equals_string: a}
postconditions:
slot_conditions:
status: {equals_string: ok}
level: {minimum_value: 1}# data.yaml
- {kind: a, status: ok, level: 2}
- {kind: a, status: bad, level: 0}
- {kind: b, status: bad, level: 0}# both.yaml
plugins:
JsonschemaValidationPlugin: {closed: true}
linkml.validator.plugins.shacl_validation_plugin.ShaclValidationPlugin: {}linkml-validate --config both.yaml -s schema.yaml -C Thing data.yaml
Rule 1 of class 'Thing': skipped, because its conditions match none of the translated patterns (...), and its postconditions constrain more than one slot.
[ERROR] [data.yaml/1] 'ok' was expected in /status
[ERROR] [data.yaml/1] 0 is less than the minimum of 1 in /level
Expected: SHACL reports item 1 too, with one result for ex:status (value "bad") and one for ex:level (value 0).
Why
The postconditions are one class expression: all of its slot conditions must hold. ¬(P → A ∧ B) ≡ (P ∧ ¬A) ∨ (P ∧ ¬B).
Proposed fix (choose one)
- One query. Combine the per-slot patterns of
_postcondition_violationsin aUNION, with each branch binding its own property:{ BIND(<…/status> AS ?path) … } UNION { BIND(<…/level> AS ?path) … }.- Project
SELECT DISTINCT $this ?path ?value. The current(<iri> AS ?path)is not allowed once?pathis bound in the pattern: SPARQL 1.1 §18.2.1 says "In SELECT, the variable v must not be in-scope in the graph pattern of the SELECT clause". rdflib accepts it anyway. - A bound IRI
?pathbecomessh:resultPath(SHACL §5.3.2).
- Project
- One
sh:SPARQLConstraintper postcondition slot, reusing the single-slot composition unchanged. The cost is several constraints with the samesh:message.
Scope
The rules example in docs/schemas/advanced.md also uses slot-level any_of (precondition) and pattern (postconditions). Neither is supported, so that example stays skipped after this change.
Acceptance criteria
test_compose_agrees_with_json_schemahas rows with two postcondition slots, includingopen_worldand a slot violated only by its absence.- There is one result per violated slot and value, with that slot's
sh:resultPath. - Verdicts for single-slot rules are unchanged.
Activity