fix(gen-shacl): enforce operator exactness, nested-slot parity, numeric bounds#22
Open
jdsika wants to merge 1 commit into
Open
Conversation
This was referenced Jul 11, 2026
c638d50 to
d0a7316
Compare
3 tasks
…ic bounds Resolve the adversarial-audit findings on the rule-converter stack. Every fix carries regression tests; 18 of the 19 new tests fail on the pre-fix source (the 19th locks in already-correct zero-member has_member semantics that had no coverage). - Operator exactness (audit A1/B2/C2): named patterns and the compositional fallback now require their conditions to set EXACTLY the operators they translate. A new _set_operator_fields helper enumerates the constraint-bearing fields actually set on a condition or class expression (metadata excluded; scalars never judged by truthiness, so minimum_value: 0 still counts). Previously a condition mixing a recognized operator with an unrecognized one (equals_string + pattern), expression-level any_of/all_of/none_of/exactly_one_of, co-set equals_string + equals_string_in, or value_presence: ABSENT combined with a bound was partially translated, silently dropping conjuncts and producing demonstrated false positives. All such rules are now skipped (skip, never mis-translate). - Boolean-guard range gate (audit A2): the boolean-guard pattern only dispatches when the target slot's induced range is boolean. An equals_string "true" postcondition on a string-range slot previously hijacked the boolean comparison and flagged conforming data; it now dispatches to presence-implies-value, which compares the string. - Nested inner-slot parity (audit B1/C1): _member_conditions resolves the container slot in the induced context of the outer class, requires its range to be a class, and resolves inner slot URIs and enum values against that range class. Previously inner slots resolved against the OUTER class: a slot_usage slot_uri override on the member class made sh:path and the SPARQL body diverge (constraint silently never fired), and an inner slot name colliding with an outer slot_usage override queried a predicate members never carry, making has_member's FILTER NOT EXISTS vacuously true (false positives on conforming data). The _resolve_member_enum_ref special case is subsumed by passing the range class to _resolve_enum_value_ref. - Numeric bound gate (audit B3/C3): _sparql_number returns None for anything but int / finite float (bool excluded), and callers skip the rule. The metamodel range of minimum_value/maximum_value is Anything: a string bound produced unparsable SPARQL that poisoned the entire shapes graph at validation time, and a YAML date parsed as arithmetic (2020-01-01 = 2018), silently never firing. Docstring corrected. - elseconditions warning (audit B4): rules with an else branch now log a warning that only the forward direction is enforced, consistent with the bidirectional/open_world warnings. - Test hygiene: the vacuous "<Manual> not in query" assertion now checks the full IRI form; new zero-member has_member end-to-end test. Signed-off-by: Carlo van Driesten <carlo.van-driesten@vdl.digital> - Alias-form and unknown rule keys (audit C4): _rule_slot resolves a rule condition key via the class's induced slots, then the underscored alias form (my_slot for a slot named "my slot"), then the base slot. _slot_uri returns None for names that resolve to no slot and every converter skips the rule -- previously an unknown or alias-form key fabricated a default_prefix predicate no shape uses, silently emitting a vacuous constraint (or an always-firing has_member).
15c4d0d to
4b2ef26
Compare
d0a7316 to
b9287ac
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves every substantive finding from the adversarial audit of the
rule-converter stack (see the audit comments on #19, #20, #21). One commit,
21 regression tests; 20 fail on the pre-fix source (the 21st locks in
already-correct zero-member
has_membersemantics that had no coverage).any_of/all_of/none_of/exactly_one_of, co-setequals_string+equals_string_in,value_presence: ABSENTcombined with a bound — all partially translated, dropping conjuncts → demonstrated false positives)_set_operator_fieldsenumerates the constraint-bearing fields actually set on a condition / class expression (metadata excluded; scalars never judged by truthiness, sominimum_value: 0still counts). Named patterns and the fallback dispatch only when their conditions set exactly the operators they translate; everything else is skipped.equals_string: "true"on a string-range slot hijacked into a boolean comparison, flagging conforming data)boolean; otherwise the rule flows to presence-implies-value, which compares the string.slot_usageslot_urioverrides diverged fromsh:path→ constraints silently never fired; name collisions with outer-class overrides madeFILTER NOT EXISTSvacuously true → false positives)_member_conditionsresolves the container slot in the outer class's induced context, requires its range to be a class, and resolves inner slot URIs and enum values against that range class. Containerslot_usagerange-narrowing now picks the right enum too (_resolve_member_enum_refsubsumed by passing the range class to_resolve_enum_value_ref)."abc"→ unparsablesh:selectpoisoning the whole shapes graph; YAML date → arithmetic2020-01-01 = 2018, silently never firing;.nan, booleans)_sparql_numberreturnsNonefor anything butint/ finitefloat(bool excluded) and callers skip the rule. Docstring corrected (the metamodel range isAnything, not int/float).elseconditionssilently droppedbidirectional/open_worldwarnings.my_slotfor a slot namedmy slot, or a typo'd key, fabricated adefault_prefixpredicate no shape uses → silently vacuous constraint, or an always-firinghas_member)_rule_slotresolves rule keys via the class's induced slots, then the underscored alias form, then the base slot;_slot_urireturnsNonefor unresolvable names and every converter skips the rule."<Manual>" not in queryassertion)has_memberend-to-end test added; assertion now checks the full IRI form.Areas of uncertainty
All substantive audit findings, including C4 (alias-form / unknown rule keys),
are addressed here.
\b/\fescaping was verified a non-issue (both arelegal raw characters in SPARQL
STRING_LITERAL2).Stack position
When upstreaming, fold each fix into the submission that introduces the
affected code (exactness + boolean gate → framework/PIV; nested parity +
numeric gate → fallback). These fixes also apply verbatim to the consolidated
branch of #18 (cherry-pick
b9287ac1).How was this tested?
tests/linkml/test_generators/test_shaclgen.py: 127 passed (106 baseassertions, and pyshacl end-to-end with
advanced=True).generator source, 20 of the 21 new tests fail — the tests bite.
ruff checkandruff format --checkclean on both changed files.Checklist
AI Assistance
If you used AI tools while preparing this PR, you are still the author and responsible for understanding, verifying, and defending your submission. Please engage with reviewers personally rather than through your agent during feedback and revisions. See our AI Covenant for details.