Skip to content

feat(generators): enforce has_member alike in SHACL and JSON Schema - #34

Open
jdsika wants to merge 1 commit into
feat/shaclgen-inlined-range-sh-nodefrom
feat/shaclgen-has-member-qualified-value-shape
Open

jdsika wants to merge 1 commit into
feat/shaclgen-inlined-range-sh-nodefrom
feat/shaclgen-has-member-qualified-value-shape

Conversation

@jdsika

@jdsika jdsika commented Oct 2, 2026 •

Copy link
Copy Markdown

2026-10-14: rebased onto the rebased #30; no code change.

Stacked on #30. Covers upstream linkml#2465 (gen-shacl ignores has_member).

Change

has_member asks for "at least one member satisfying the condition" of a multivalued slot. Both generators now read it the same way: a member is one value, which must satisfy the value operators, range and range_expression of the expression.

slots:
  tags:
    range: string
    multivalued: true
    has_member: {equals_string: reviewed}
tags before: SHACL before: JSON Schema now: both
absent valid valid invalid
[] valid invalid invalid
[pending] valid invalid invalid
[reviewed, pending] valid valid valid
  • gen-shacl ignored has_member. It now emits sh:qualifiedValueShape with sh:qualifiedMinCount 1 (SHACL §4.7.3), on slots and in slot conditions of class-level expressions.
  • gen-json-schema applied only the member's value operators, accepted an absent slot, and ignored has_member in class-level expressions. It now emits contains on a required slot. For a slot inlined as a dict it emits not: {additionalProperties: {not: <member>}}, because contains doesn't apply to objects.
  • In a slot condition, has_member decides presence (an absent slot fails it), and the member takes the form of the condition: "definitely true" under none_of.

Decisions

Other generators

Validation

  • test_has_member.py: 48 cases. They validate the same instance with both generated artifacts (RDF through the generated JSON-LD context), plus SHACL-only and JSON-only cases. 34 fail on fix(generators): enforce range_expression on each value alike in SHACL and JSON Schema #30.
  • Out-of-tree check during development: 480 random schemas and 5,760 instances (has_member on slots and in class-level conditions, with member range_expression). gen-shacl, gen-json-schema and an independent three-valued model agreed on all of them. That harness is not part of this PR.
  • Mutation: 21 mutants of the new code, all killed; one by the doctest of states_presence.
  • Sweep: across 387 schemas in the repository, SHACL and JSON Schema output is unchanged, with identical errors and warnings.
  • Compliance: test_membership now expects SHACL to implement has_member, except on references.
  • Tests: the agreement harness of fix(generators): enforce range_expression on each value alike in SHACL and JSON Schema #30's tests moves to tests/linkml/utils/generator_agreement.py, shared with these tests.
  • CI: see the checks on the current head.

@jdsika jdsika self-assigned this Oct 2, 2026
@jdsika
jdsika force-pushed the feat/shaclgen-has-member-qualified-value-shape branch from 21e4983 to a0b2c05 Compare October 2, 2026 15:08
@jdsika
jdsika changed the base branch from feat/shaclgen-class-boolean-expressions to feat/shaclgen-inlined-range-sh-node October 2, 2026 15:08
@jdsika
jdsika force-pushed the feat/shaclgen-inlined-range-sh-node branch from 5db175a to 111a453 Compare October 2, 2026 15:13
@jdsika
jdsika force-pushed the feat/shaclgen-has-member-qualified-value-shape branch from a0b2c05 to 883509a Compare October 2, 2026 15:13
@jdsika
jdsika force-pushed the feat/shaclgen-inlined-range-sh-node branch 3 times, most recently from 937b986 to 3f14cf9 Compare October 5, 2026 09:52
@jdsika
jdsika marked this pull request as draft October 5, 2026 11:26
@jdsika jdsika changed the title feat(gen-shacl): translate has_member to sh:qualifiedValueShape feat(generators): enforce existential collection membership Oct 5, 2026
@jdsika
jdsika force-pushed the feat/shaclgen-has-member-qualified-value-shape branch from 883509a to c94f379 Compare October 5, 2026 11:47
@jdsika
jdsika marked this pull request as ready for review October 5, 2026 12:01
@jdsika
jdsika force-pushed the feat/shaclgen-inlined-range-sh-node branch 2 times, most recently from 68430f4 to 46c2ab1 Compare October 7, 2026 20:03
@jdsika
jdsika force-pushed the feat/shaclgen-has-member-qualified-value-shape branch from c94f379 to 23553b9 Compare October 7, 2026 21:09
@jdsika jdsika changed the title feat(generators): enforce existential collection membership feat(generators): enforce has_member alike in SHACL and JSON Schema Oct 7, 2026
has_member asks for at least one value of a multivalued slot satisfying
an expression. gen-shacl ignored it. gen-json-schema translated it as
contains, which an absent slot passes but an empty one fails, applied
only the value operators of the expression, and ignored it in
class-level expressions.

Both generators now read a member as one value, which must satisfy the
value operators, range and range_expression of the expression. A slot
without a value has no member, so has_member decides presence. In SHACL
it is an sh:qualifiedValueShape with sh:qualifiedMinCount 1; in JSON
Schema, contains on a required slot, or some value of a dict inlined by
key. In a slot condition the member takes the form of the condition.
SHACL skips a has_member it cannot express with a warning.

Signed-off-by: jdsika <[email protected]>
@jdsika
jdsika force-pushed the feat/shaclgen-inlined-range-sh-node branch from 46c2ab1 to 3f2a2ba Compare October 14, 2026 05:44
@jdsika
jdsika force-pushed the feat/shaclgen-has-member-qualified-value-shape branch from 23553b9 to a7becc8 Compare October 14, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant