Commit d0a7316
committed
fix(gen-shacl): enforce operator exactness, nested-slot parity, numeric 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>1 parent 15c4d0d commit d0a7316
2 files changed
Lines changed: 1009 additions & 87 deletions
File tree
- packages/linkml/src/linkml/generators
- tests/linkml/test_generators
0 commit comments