feat(gen-shacl): translate presence-implies-value rules to SHACL-SPARQL - #19
feat(gen-shacl): translate presence-implies-value rules to SHACL-SPARQL#19jdsika wants to merge 1 commit into
Conversation
Adversarial audit findings (PIV converter, audited at stack tip incl. the hardening PR)Two substantive findings, both empirically demonstrated with pyshacl end-to-end probes and cross-checked against LinkML's reference rule semantics ( A1 — real bug: greedy dispatch drops extra pre/postcondition operators → false positives. A2 — edge case: boolean-guard shadows PIV for Verified clean (attacked, held up): absent-target semantics (violation via Test gaps (cosmetic): These findings equally apply to the consolidated branch of #18. Suggest addressing A1/A2 as a follow-up commit on this stack before upstreaming. |
2e56a36 to
8999ad6
Compare
The rules-to-SHACL-SPARQL converter added in linkml#3451 recognised a single named pattern. This adds the presence-implies-value pattern: a precondition asserting `value_presence: PRESENT` on one slot, and a postcondition constraining another slot with `equals_string` or `equals_string_in`. It reads as "if the guard slot is present, the target slot must be present and hold one of the allowed values", and generalises the existing boolean guard to arbitrary enum values. The boolean guard is now gated on the target slot's range actually being `boolean`. Without that gate a slot of range `string` carrying `equals_string: "true"` was translated as a boolean comparison, which does not match the string `"true"` in the data and so flagged conforming instances as violations. String-ranged slots now fall through to the presence-implies-value pattern and compare as strings. Pattern matching is exact: each converter requires its conditions to set precisely the operators it translates. A rule whose conditions carry anything further -- extra scalar operators, or expression-level any_of / all_of / none_of / exactly_one_of -- is skipped rather than partially translated, since dropping a term would either widen the precondition (false positives) or weaken the postcondition (false negatives). Slot resolution goes through induced slots so that `slot_usage` overrides, `slot_uri` overrides and alias-form keys resolve to the same IRI that `sh:path` emits. Co-authored-by: jdsika <carlo.van-driesten@vdl.digital>
8999ad6 to
e161ce7
Compare
|
Mirrored upstream as linkml#3989. |
Summary
Adds the presence-implies-value pattern to the rules → SHACL-SPARQL
converter: a precondition asserting
value_presence: PRESENTon a guard slotplus a postcondition constraining another slot with
equals_string/equals_string_inbecomes ash:SPARQLConstraintthat flags focus nodes wherethe guard is present but the target does not hold an allowed value.
It reads as "if the guard slot is present, the target slot must be present and
hold one of the allowed values" and generalises the existing boolean guard to
arbitrary enum values. Enum permissible values resolve to their
meaningIRIs;values without
meaningcompare as string literals.$thisis pre-bound perSHACL §5.3.1.
Example from the modeled use case: if
sun_altitudeis present,daytimemustbe
dayortwilight— cross-parameter consistency that per-slot SHACLproperty shapes cannot express.
Bug fixed along the way
The pre-existing boolean guard was not gated on the target slot's range. A slot
of range
stringcarryingequals_string: "true"was translated as a booleancomparison, which does not match the string
"true"in the data — so conforminginstances were reported as violations. The guard now requires range
boolean,and string-ranged slots fall through to the presence-implies-value pattern and
compare as strings.
Translation contract: skip, never mis-translate
Pattern matching is exact. Each converter requires its conditions to set
precisely the operators it translates. A rule carrying anything further — extra
scalar operators, or expression-level
any_of/all_of/none_of/exactly_one_of— is skipped rather than partially translated, because droppinga term would either widen the precondition (false positives) or weaken the
postcondition (false negatives).
Slot resolution goes through induced slots, so
slot_usageoverrides,slot_urioverrides and alias-form keys all resolve to the same IRI thatsh:pathemits.Stack position
Note
No dependencies. This PR applies directly to
main. The rules → SPARQLframework it extends (
_add_rules,_rule_to_sparql, the boolean-guard andexclusive-value patterns) is already in
mainvia linkml#3451.Testing
pytest tests/linkml/test_generators/test_shaclgen.py— 105 passed, withthe full generator suite green (1774 passed, 52 skipped, 3 xfailed).
Coverage includes SPARQL syntax validation and
pyshaclend-to-end conforming/ violating round-trips for each pattern, plus negative tests asserting that
unsupported operator combinations are skipped rather than mis-translated.
Review notes
This PR was re-cut from an earlier five-PR stack. The corrections that were
previously separate follow-ups are folded into the feature they correct, so
there is no longer a PR that introduces a defect and another that fixes it.
Two defects found during that re-cut are fixed here: the boolean-guard range
gate above, and a duplicate
test_rule_with_elseconditions_warnsdefinitionthat silently shadowed the existing test of the same name.