Repository navigation
feat(gen-shacl): add a compositional fallback for rule-to-SPARQL conversion - #20
Conversation
Adversarial audit findings (fallback converters, audited at stack tip incl. the hardening PR)Findings demonstrated with pyshacl end-to-end probes against the stack tip; all apply equally to the consolidated branch of #18. B1 — real bug: M4/M5 inner-slot paths resolve against the OUTER class.
B2 — real bug: recognized+unrecognized operator mixes are silently under-translated → false positives (contract violation). B3 — real bug (artifact-poisoning edge): B4 — edge case: Verified clean (attacked, held up): M2 conjunction semantics for multivalued slots (correct existential violation, deduplicated reports); M3 numeric promotion for well-typed data; M5 zero-member semantics (flagged, as "must contain a member" requires — though no test locks this in: suggest adding a zero-member violating instance); variable namespaces ( Suggest addressing B1–B3 as a follow-up commit on this stack before upstreaming; the probe scripts are reusable as regression-test seeds. |
|
Status (head c48bb2a, mirrored as linkml#3990): all four findings are fixed in this PR's single commit. #21 and #22 were closed unmerged, and their fixes were folded into that commit.
Two "verified clean" statements above no longer describe the code:
|
2e56a36 to
8999ad6
Compare
8442eb8 to
1ea1ea2
Compare
8999ad6 to
e161ce7
Compare
1ea1ea2 to
59544b4
Compare
|
Mirrored upstream as linkml#3990. |
e161ce7 to
7622d70
Compare
59544b4 to
1b1c775
Compare
7622d70 to
bae4a43
Compare
1b1c775 to
d035e8c
Compare
66276ea to
1e506c2
Compare
d035e8c to
c89faac
Compare
0bc284c to
5c044e7
Compare
c89faac to
a71a8f9
Compare
…ersion A rule that no named pattern matches is now composed from the operators it uses: each precondition becomes SPARQL filters on $this, and each postcondition slot gets its own constraint, the union of the ways to violate that slot's condition, binding the offending value to ?value (SHACL sections 5.3.1 and 5.3.2). A rule P -> A and B is violated exactly when P and not A, or P and not B, holds. A condition may use value_presence, required, equals_string, equals_string_in, minimum_value, maximum_value, a range_expression on the slot's class (nested to any depth) and has_member. The semantics follow the JSON Schema generator's if/then: - presence: value_presence decides, then required, then a default: a precondition requires its slot, a postcondition unless the rule is open_world, an inner condition of a nested expression never; - value operators and range_expression apply to every value of the slot (05validation.md: "all members"), has_member to some value; - equals_string(_in) only on string, enum and boolean slots (the named patterns' range check), bounds only on the numeric datatypes of SPARQL 1.1 section 17.1, guarded with isNumeric, and only finite numbers. Any other operator, or a rule with empty pre- or postconditions, skips the rule with one warning, so a rule is never partially translated. A rule skipped part-way no longer reports the notes of the abandoned translation. The compliance suite now runs SHACL in the rule tests: a rule gen-shacl translates is checked for every instance, and for a rule it skips only valid instances are checked. Co-authored-by: jdsika <[email protected]> Signed-off-by: jdsika <[email protected]>
a71a8f9 to
c48bb2a
Compare
Mirror of linkml#3990, same head. Review there; this PR tracks the fork stack.
Summary
When no named pattern from linkml#3989 matches a rule, this PR composes the rule's SHACL-SPARQL constraint from the operators it uses. Each precondition becomes filters on
$this. Each postcondition slot gets its own constraint: the union of the ways to violate that slot's condition, with the offending value bound to?value. A ruleP → A ∧ Bis violated exactly whenP ∧ ¬AorP ∧ ¬Bholds (SHACL §5.3.1, §5.3.2).With this rule, a
Thingwhoselevelis between 10 and 20 violates the constraint when itsnoteis missing, or for eachnotevalue other thanloworhigh.Supported operators (in any condition, including nested ones):
value_presence,required,equals_string,equals_string_in,minimum_value,maximum_value,range_expressionandhas_member.Anything else is skipped with a warning, as is a rule with empty pre- or postconditions. A rule is never partially translated.
Semantics
These follow jsonschemagen's
if/then.value_presencedecides first, thenrequired, then a default:open_world;range_expressionapply to every value, as jsonschemagen does throughitems. The spec matches slot checks against "all members of the collection" (05validation.md) but doesn't define when a rule condition is satisfied. This reading is the decision recorded in Rules: value operators on a multivalued slot in rule conditions mean "every value" in some generators and "some value" in others #41, and feat(gen-shacl): translate presence-implies-value rules to SHACL-SPARQL linkml/linkml#3989 follows it too.has_memberneeds only one value. For example, the preconditionlevels: {minimum_value: 10}holds for[12, 15]but not for[12, 3].equals_string(_in)is translated only on string, enum and boolean slots (the same check as in feat(gen-shacl): translate presence-implies-value rules to SHACL-SPARQL linkml/linkml#3989)."15" >= 10as true. Bounds are therefore guarded withisNumeric.{required: true}is composed, while{value_presence: PRESENT}matches the named pattern; the tests check both against JSON Schema. Rules that feat(gen-shacl): translate presence-implies-value rules to SHACL-SPARQL linkml/linkml#3989 skipped, such asequals_stringtogether withequals_string_in, or anABSENTguard, are now translated exactly.Differences from jsonschemagen
has_memberinside rule conditions (gen-json-schema rules: deactivated rules enforced; cardinality, has_member and all_members dropped in rule conditions; class-level multivalued ignored #42); this PR enforces it.value_presence: ABSENTinto "not all present" (gen-json-schema: multiplevalue_presence: ABSENTpostconditions in one rule compile to "not both present" instead of "neither present" linkml/linkml#3719); this PR requires each slot to be absent.[]as present. In RDF an empty list leaves no triple, so the slot counts as absent.Limitations
pattern,equals_number, cardinalities, and slot-levelany_of/none_of.isNumericreturns true for literals such as"abc"^^xsd:integer, so they are still compared. The property shape reports them anyway.Testing
has_member;ABSENTconditions;sh:value;open_world. Each violated slot is reported with its ownsh:resultPath.range_expressionpostconditions:open_worldwith an omitted or empty wrapper, an empty wrapper in a closed world, a multivalued wrapper with one failing member (checked against JSON Schema in the class and a subclass), an enum value with ameaningresolved on the range class, and a condition on the range class's identifier, which is skipped with a warning.test_boolean_slot_compliance.pynow run SHACL. A rule gen-shacl translates is checked for every instance; for a rule it skips, only valid instances are checked.Closes #39 (compliance runs SHACL for rules) and closes #47 (rules with several postcondition slots).
Upstream stack: linkml#3989 → linkml#3990 → linkml#3991 (docs).
Fork stack: #19 → #20 → #23 (docs).