diff --git a/docs/generators/shacl.rst b/docs/generators/shacl.rst index 3e88f0090f..659e55f63c 100644 --- a/docs/generators/shacl.rst +++ b/docs/generators/shacl.rst @@ -84,6 +84,83 @@ Example Output: shacl:targetClass . +Rule constraints (SHACL-SPARQL) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +LinkML `rules `_ express +cross-parameter, conditional validation ("if slot A holds X, slot B must +..."). Plain per-slot SHACL property shapes cannot express these, so the +generator translates recognised rule shapes into +`SHACL-SPARQL constraints `_ +(``sh:sparql`` / ``sh:SPARQLConstraint``) on the class's ``sh:NodeShape``. +Generation is controlled by ``--emit-rules/--no-emit-rules`` (default: on). + +Three named patterns are recognised first: + +* **Boolean guard** โ€” precondition ``value_presence: PRESENT`` on a value + slot, postcondition ``equals_string: "true"`` on a *boolean-range* flag + slot: if the value is present, the flag must be true. +* **Presence implies value** โ€” precondition ``value_presence: PRESENT``, + postcondition ``equals_string`` / ``equals_string_in`` on a target slot: + if the guard is present, the target must hold one of the allowed values. + Enum values resolve to their ``meaning`` IRIs; values without ``meaning`` + compare as string literals. +* **Exclusive value** โ€” precondition ``equals_string`` and postcondition + ``maximum_cardinality`` on the *same* multivalued slot: if the value is + present, the slot has at most N values. + +Combinations outside the named patterns are handled by a compositional +fallback that conjoins the preconditions and negates a single postcondition: +conditional-required (``required: true``), conditional-absent +(``value_presence: ABSENT``), numeric threshold preconditions +(``minimum_value`` / ``maximum_value``), a one-hop nested precondition into +an inlined child object (``range_expression.slot_conditions``), and +``has_member`` list membership. + +The translation contract is *skip, never mis-translate*: a rule whose +conditions set any operator outside the translated set (including +expression-level ``any_of``/``all_of``/``none_of``/``exactly_one_of``), or +whose slot keys resolve to no slot, is skipped and logged at ``DEBUG``. +``deactivated`` rules are skipped; ``bidirectional``, ``open_world``, and +``elseconditions`` warn (the forward direction is emitted). + +Example: + +.. code-block:: yaml + + classes: + Weather: + slots: [sun_altitude, daytime] + rules: + - description: If sun_altitude is present, daytime must be day or twilight. + preconditions: + slot_conditions: + sun_altitude: + value_presence: PRESENT + postconditions: + slot_conditions: + daytime: + equals_string_in: [day, twilight] + +generates (abridged): + +.. code-block:: turtle + + ex:Weather a sh:NodeShape ; + sh:sparql [ a sh:SPARQLConstraint ; + sh:message "If sun_altitude is present, daytime must be day or twilight." ; + sh:select """SELECT $this WHERE { + $this ?value . + OPTIONAL { $this ?target . } + FILTER ( !BOUND(?target) || ?target NOT IN (, ) ) + }""" ] . + +``$this`` is pre-bound to each focus node per +`SHACL ยง5.3.1 `_. +Note that SPARQL-based constraints require a SHACL processor with +SHACL-SPARQL support (e.g. ``pyshacl`` with ``advanced=True``). + + Command Line ^^^^^^^^^^^^