Skip to content

A SHACL shape gives a property the domain and range it does not declare - #1001

Merged
WaylandYang merged 2 commits into
deeplethe:devfrom
Maya-Kid:fix/shacl-shapes-give-domain-and-range
Sep 29, 2026
Merged

WaylandYang merged 2 commits into
deeplethe:devfrom
Maya-Kid:fix/shacl-shapes-give-domain-and-range

Conversation

@Maya-Kid

Copy link
Copy Markdown
Contributor

For #923, as suggested there.

UCO states its constraints as SHACL shapes rather than as rdfs:domain / rdfs:range, so its properties arrive at import with almost no domains. A datatype property without a domain is skipped at import. This change reads the shapes.

Problem

  • The projection reads a signature from rdfs:domain / rdfs:range, and from schema.org's domainIncludes / rangeIncludes.
  • UCO 1.5.0 declares 6 domains for 756 properties. Its constraints live in node shapes, for example:
    observable:AccountAuthenticationFacet a owl:Class, sh:NodeShape ;
        sh:property [ sh:path observable:passwordType ; sh:datatype xsd:string ] ;
        sh:targetClass observable:AccountAuthenticationFacet .
  • An attribute without a domain is skipped at import (no_domain). On the merged UCO, 561 of the 579 datatype properties are skipped for this reason, and only 2 would be imported.
  • The property shapes are blank nodes, and read_triples drops every triple whose subject is a blank node.

Change

Keeping the shape triples. In crates/utopia-ingest/src/ontology_rdf.rs, read_triples now keeps the three triples a property shape needs (sh:path, sh:class, sh:datatype) when their subject is a blank node. It records a blank-node object in a new field that only the shape pass reads. Every other blank-node triple is dropped as before, and triples still counts only triples whose subject is an IRI.

Reading the shapes. shape_signatures reads node shape sh:property property shape:

  • the property is the property shape's sh:path, when that path is an IRI;
  • its domain is the class the node shape targets: sh:targetClass, or the node itself when it is both a class and a sh:NodeShape (SHACL's implicit class target);
  • its range is the property shape's sh:class or sh:datatype.

Declarations win. project uses these only where the property declares no domain or range of its own.

  • A property constrained on several classes gets all of them as its domain, read the way domainIncludes is.
  • Ranges that come from different shapes are alternatives (ranges_union), as with rangeIncludes.

Not read. Each of these is noted in the code:

  • sh:targetSubjectsOf and sh:targetObjectsOf, which do not name a class;
  • inverse and sequence paths;
  • sh:or and sh:node.

The report. sh:targetClass, sh:property, sh:path, sh:class and sh:datatype are no longer reported as not projected. The other SHACL predicates still are.

The record. In docs/decisions/0001-ontology-import-and-governance.md, decision 13 now says where a signature comes from when a property declares none, and a revision entry records the change.

None of the five bundled packs mentions SHACL, so their projection is unchanged.

Verification

Run in containers on a Linux host: Rust 1.98.1 and PostgreSQL 16 with pgvector.

New tests (ontology_rdf::shape_tests)

One Turtle snippet covers each form:

  • a class that is its own shape, with an explicit sh:targetClass and blank-node property shapes carrying sh:class and sh:datatype;
  • a class that is a sh:NodeShape without sh:targetClass;
  • a named node shape that points at a named property shape;
  • a property constrained on two classes, which gets two domains and two ranges read as alternatives;
  • a declared rdfs:domain / rdfs:range, which wins over the shape;
  • forms that are not read: an inverse path, sh:or, sh:targetSubjectsOf, and a path to a property the file does not declare;
  • the consumed SHACL predicates, which are not reported as not projected; the triple count still counts only IRI subjects.

On dev with only the new tests, three of the four fail. The one that passes is the test that a declared domain and range win: it pins that a shape never overrides a declaration.

This branch

  • cargo fmt --all --check and cargo clippy --locked --workspace --all-targets -- -D warnings are clean.
  • UTOPIA_TEST_REQUIRE_PDFTOTEXT=1 cargo test --locked --workspace passes: 1297 passed, 0 failed. This includes the projection tests against the bundled packs.

UCO 1.5.0 through the import preview

The 18 Turtle files under ontology/ were merged by concatenation; they have no labelled blank nodes and no relative IRIs, so that is a valid merge. The import plan that the preview returns was run into an empty base, on dev and on this branch:

dev this branch
Properties (object / datatype) 756 (177 / 579) 756 (177 / 579)
With a domain 6 753
With a range 722 748
With both 3 748
Datatype properties skipped for having no domain 561 1
  • Imported. 549 datatype properties are imported instead of 2. The 30 still skipped are:
    • the same 16 key collisions;
    • 13 whose range is xsd:hexBinary or xsd:base64Binary, which the importer does not map today;
    • core:informalType, a super-property that no shape constrains.
  • Examples.
    • observable:passwordType gets AccountAuthenticationFacet → xsd:string.
    • core:hasFacet gets UcoObject → Facet.
    • core:name gets all eight classes that constrain it.
  • Still without a domain or range. Three properties have no domain and eight have no range. Their shapes use forms this change does not read:
    • sh:or over datatypes;
    • a literal with no datatype;
    • a property shape targeting the subjects of its own path.

Signed-off-by: dada-yan <BinjunYann@gmail.com>

@WaylandYang WaylandYang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Thanks @Maya-Kid.

Reviewed at df75d62. What I checked:

  • Only the gaps are filled. A declared rdfs:domain / rdfs:range wins, side by side, and a test pins it.
  • Blank nodes stay contained. Only sh:path, sh:class and sh:datatype are kept for a blank subject, the blank object sits in a field nothing else reads, and the triple count is unchanged. The members of an sh:or list are kept too but never reached, since no node shape lists them under sh:property.
  • What is not read is said, in the code and in the tests, and still shows up in the report.

The UCO table answers what I was going to ask: 549 datatype properties imported where there were 2, and the 30 left over each have a reason. The 13 with xsd:hexBinary / xsd:base64Binary look like a small follow-up of their own if you want it.

@WaylandYang
WaylandYang merged commit 40397a5 into deeplethe:dev Sep 29, 2026
@Maya-Kid
Maya-Kid deleted the fix/shacl-shapes-give-domain-and-range branch September 29, 2026 06:13
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.

2 participants