Repository navigation
fix(generators): enforce range_expression on each value alike in SHACL and JSON Schema - #30
Open
jdsika wants to merge 1 commit into
Conversation
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 2, 2026 15:06
aee8951 to
5db175a
Compare
jdsika
changed the base branch from
main
to
feat/shaclgen-class-boolean-expressions
October 2, 2026 15:06
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 2, 2026 15:13
5db175a to
111a453
Compare
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 5, 2026 07:15
111a453 to
74a2483
Compare
jdsika
marked this pull request as draft
October 5, 2026 07:28
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
2 times, most recently
from
October 5, 2026 09:52
937b986 to
3f14cf9
Compare
jdsika
marked this pull request as ready for review
October 5, 2026 10:06
jdsika
force-pushed
the
feat/shaclgen-class-boolean-expressions
branch
from
October 7, 2026 12:05
760eb0d to
4a9d475
Compare
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
2 times, most recently
from
October 7, 2026 20:03
68430f4 to
46c2ab1
Compare
jdsika
force-pushed
the
feat/shaclgen-class-boolean-expressions
branch
from
October 14, 2026 05:29
4a9d475 to
48e9234
Compare
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 14, 2026 05:44
46c2ab1 to
3f2a2ba
Compare
jdsika
force-pushed
the
feat/shaclgen-class-boolean-expressions
branch
from
October 23, 2026 08:49
48e9234 to
23b5c25
Compare
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 23, 2026 08:49
3f2a2ba to
38285fa
Compare
jdsika
force-pushed
the
feat/shaclgen-class-boolean-expressions
branch
from
October 23, 2026 09:03
23b5c25 to
78870fa
Compare
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 23, 2026 09:06
38285fa to
c1527eb
Compare
jdsika
force-pushed
the
feat/shaclgen-class-boolean-expressions
branch
from
October 23, 2026 10:48
78870fa to
edcb032
Compare
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 23, 2026 10:52
c1527eb to
19b8129
Compare
…L and JSON Schema
gen-shacl ignored a slot's range_expression, and gen-json-schema read it
with the two-valued builder of rules, which ignores cardinalities, the
range of a condition, and the slot_usage and aliases of the value class.
Both generators now read it as a class-level expression: each value must
satisfy it as an instance of the range class satisfies a class-level
expression. In SHACL it is an sh:node next to the range's sh:class. On a
class alternative of any_of it applies to that alternative, and in a
slot condition it takes the form of the condition. SHACL skips an
expression it cannot express with a warning, as it skips a class-level
expression.
gen-json-schema applied the value constraints of a slot inlined as a
dict to the dict instead of to each value, which only a range_expression
showed. The compact forms of such a dict, {id: value} and {id: null},
stay out of its reach.
Signed-off-by: jdsika <[email protected]>
jdsika
force-pushed
the
feat/shaclgen-class-boolean-expressions
branch
from
October 23, 2026 10:56
edcb032 to
e6a40da
Compare
jdsika
force-pushed
the
feat/shaclgen-inlined-range-sh-node
branch
from
October 23, 2026 10:58
19b8129 to
baff325
Compare
3 tasks done
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.
Mirror of linkml#4169, same head. Review there; this PR tracks the fork stack and keeps the fork review below.
Stacked on #29 (upstream linkml#4028), whose three-valued reading of class-level expressions it reuses.
Change
A slot's
range_expressionconstrains each of its values. Both generators now read it the same way: a value must satisfy the expression as an instance of the range class satisfies a class-level expression.license{}{category: retracted}{category: draft}range_expression. It now emits ansh:nodenext to the range'ssh:class(SHACL §4.7.1). This applies on slots, on class alternatives ofany_of, and in slot conditions, where the expression takes the form of the condition: its "definitely true" form undersh:not.range, readsnone_ofandexactly_one_oftwo-valued, and resolves slots globally, missing the value class'sslot_usageand aliases. It now uses feat(generators): translate class-level boolean expressions alike to SHACL and JSON Schema #29's class-expression builder. This changes the generated schema for everyrange_expressionin use; the table shows the effect.main: for a slot inlined as a dict, the slot's own value constraints applied to the dict rather than to each value, so valid data was rejected.range_expressionis the only value constraint a class-valued slot carries, so nothing else showed it. Fixed here, because the contract above depends on it. The matching defect of feat(generators): translate class-level boolean expressions alike to SHACL and JSON Schema #29's class-expression builder, which demanded an array for such a slot, moved to feat(generators): translate class-level boolean expressions alike to SHACL and JSON Schema #29.Decisions
equals_expression: not translated. An earlier version translated constantequals_expressionin SHACL conditions only. Neither generator enforces it on slots, so that would have introduced a second inconsistency. It is a separate feature. (The metamodel's ownslot_groupuses it, so gen-shacl now logs one warning formeta.yaml.)is_acombinations of types and enums are translated; the metamodel describesrange_expressionas "combining existing ranges", e.g. two enums. A slot condition on such a value has no slots to constrain: SHACL skips it with a warning, and JSON Schema'spropertiesare vacuous on a scalar, so both accept the value.is_a: <type>/is_a: <enum>: the metamodel narrowsis_aof ananonymous_class_expressiontoclass_definition("A class that any instance satisfying this expression must also be an instance of",slot_usageinmeta.yaml), while itsrange_expressioncomment envisions combining two enums, and cmungall writes in discussion #2791 that the metamodel "arguably ... should allow" an expression over named ranges. The compliance suite (test_range_expression_booleans,is_a: integer) and gen-json-schema already read it that way, so both generators keep doing so; the metamodel gap is not resolved here.imports_closure()call: no case needed it; the imported-slot test passes without it.Other generators
transform_class_expressionhas emitted open-world restrictions forrange_expressionsince Implementing range_expression in jsonschemagen linkml/linkml#2860; unchanged.range_expressionsupport; unchanged.Limitations
{id: value}and{id: null}of a slot inlined as a dict are not constrained by the expression; SHACL rejects the resources they denote. Recorded bytest_range_expression_on_compact_dict_forms(strict xfail) and in the generator docs.Validation
test_range_expression.py: 113 cases plus the 2 xfails. They validate the same instance with both generated artifacts (RDF through the generated JSON-LD context), plus SHACL-only cases; 32 failed on the previous head of this PR and 71 on feat(generators): translate class-level boolean expressions alike to SHACL and JSON Schema #29.any_ofalternatives, class-level conditions). gen-shacl, gen-json-schema and an independent three-valued model agreed on all of them; the previous head had 3,796 disagreements. That harness is not part of this PR.rangeversus slotrangein JSON Schema) is equivalent there; its SHACL counterpart is killed.slot_group, which usesequals_expression.test_range_expression_nestingnow expects SHACL to implement nested range expressions (it fails on feat(generators): translate class-level boolean expressions alike to SHACL and JSON Schema #29).Found, not addressed here (they predate this PR):
value_presenceinside aslot_usagerange_expression.