Skip to content

feat(generators): enforce existential collection membership - #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

A multivalued slot's has_member requires at least one value satisfying an expression. SHACL previously ignored it, accepting collections with no matching member. Emit sh:qualifiedValueShape with sh:qualifiedMinCount 1, on ordinary slots and contextual class slot conditions. Correct JSON Schema's corresponding contains translation so it retains explicit member ranges, empty expressions, collection inlining and the owning class's slot context.

This is stacked on #30's reviewed contextual-range implementation. No new flag: ordinary class ranges retain their RDF class-membership requirement, and range_expression supplies contextual constraints.

Contract and references

  • LinkML has_member defines an existential requirement on a multivalued slot. Declare multivalued: true; single-valued SHACL uses are rejected.
  • SHACL 4.7.3 counts matching values. Multiple requirements may share a witness; qualified shapes are not made disjoint.
  • JSON Schema contains applies the member schema to array elements.
  • Upstream issue #2465 requests this missing SHACL translation. Its example needs an explicit multivalued declaration under the metamodel definition.

Reuse the scalar slot-condition translator and its supported-field definitions rather than maintaining another member-specific list. Cover explicit ranges, numeric bounds, patterns, string/numeric equality, scalar constant equals_expression, and nested range_expression. Repeated numeric constraints remain conjunctive. Enum meanings and URI-family class identifiers use RDF terms. Unsupported slot-level member expressions fail instead of silently disappearing; the existing whole-operator warning/skip behavior for unsupported class logical expressions is retained.

Cross-generator findings and boundaries

  • SHACL and JSON Schema have executable positive/negative parity tests for the supported common cases. JSON fixes include contextual membership in class expressions and rules, explicit class ranges retaining inlining, and has_member: {} requiring a member.
  • JSON distinguishes omitted optional properties from empty arrays; ordinary RDF property arcs cannot. contains does not validate an omitted optional property, while the SHACL qualified minimum rejects zero arcs. Use required: true for portable existence requirements. This boundary is tested and documented, not hidden behind an option.
  • OWL already emits existential restrictions, under open-world semantics: absence of a stated witness alone is not an inconsistency. ShEx, Python and Pydantic do not currently enforce these expressions. all_members and direct boolean operators inside SHACL member expressions remain unsupported. JSON Schema does not translate scalar equals_expression; use dedicated equality fields for portable constraints.
  • JSON structural class validation and RDF class membership, and JSON numeric subtyping versus RDF datatype identity, remain format-specific.

Origin and validation

The downstream SimulationManifest requirement is generic: at least one artifact from each of four categories. It is expressible with existing all_of, slot_conditions, has_member and range_expression; no project vocabulary is encoded in the compiler. A read-only consumer probe used the actual migrated OMB schema's generated membership expressions and the existing missing-media fixture's category projection: rejection without media, acceptance with media, rejection when any category is removed, and rejection of an uncategorized substitute. This focused probe is not a claim of complete downstream model equivalence; the real OMB checkout remains unchanged.

  • Full compiler suite, including slow and Biolink tests: 12,555 passed, 1,960 skipped, 15 xfailed; 261 subtests passed (notebooks tested through docs; external Kroki excluded).
  • Focused SHACL/contextual-range/JSON Schema run: 418 passed, 1 skipped; 261 subtests passed; subsequent full run includes the additional owning-class regression.
  • Membership compliance: 236 passed, 11 skipped; primitive SHACL membership cases promoted to required implementation checks.
  • Real Draft 2020-12 and pySHACL validation, including meta-SHACL; no mock validators or weakened failure assertions.
  • Strict Sphinx build including notebooks, all pre-commit hooks, compiler wheel/sdist build.

Final head c94f379d131964e4c7c6ec10bef8756975957b5a is one signed commit on the revised #30 base. All 17 exact-head CI checks passed, including the package build: workflow.

@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
Translate has_member to SHACL qualified minimum counts using the shared
contextual value translator. Preserve ordinary class membership, reject
unsupported slot-level expressions, and keep repeated bounds conjunctive.

Correct JSON Schema contains for explicit ranges, empty expressions,
inlined members and contextual collection metadata. Validate generated
artifacts with both consumers and document representation boundaries.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@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
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