From b87467cc920abbd0fea9fbc43ffaea140d823d99 Mon Sep 17 00:00:00 2001 From: Douglas Lowe <10961945+douglowe@users.noreply.github.com> Date: Mon, 15 Sep 2025 16:21:52 +0100 Subject: [PATCH 01/18] 5s-crate profile file --- .../profiles/five-safes-crate/profile.ttl | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 rocrate_validator/profiles/five-safes-crate/profile.ttl diff --git a/rocrate_validator/profiles/five-safes-crate/profile.ttl b/rocrate_validator/profiles/five-safes-crate/profile.ttl new file mode 100644 index 00000000..75946bfe --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/profile.ttl @@ -0,0 +1,81 @@ +# Copyright (c) 2024-2025 CRS4, University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Five Safes RO-Crate 0.4" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Five Safes RO-Crate Metadata Specification 0.4"""@en ; + + # URI of the publisher of the Metadata Specification + dct:publisher ; + + # This profile is an extension of Workflow Run Crate for use in Trusted Research Environments (TRE) + prof:isProfileOf ; + + # This profile is a transitive profile of the RO-Crate Metadata Specification + prof:isTransitiveProfileOf , + , + ; + + # this profile has a JSON-LD context resource + prof:hasResource [ + a prof:ResourceDescriptor ; + + # it's in JSON-LD format + dct:format ; + + # it conforms to JSON-LD, here refered to by its namespace URI as a Profile + dct:conformsTo ; + + # this profile resource plays the role of "Vocabulary" + # described in this ontology's accompanying Roles vocabulary + prof:hasRole role:Vocabulary ; + + # this profile resource's actual file + prof:hasArtifact ; + ] ; + + # this profile has a human-readable documentation resource + prof:hasResource [ + a prof:ResourceDescriptor ; + + # it's in HTML format + dct:format ; + + # it conforms to HTML, here refered to by its namespace URI as a Profile + dct:conformsTo ; + + # this profile resource plays the role of "Specification" + # described in this ontology's accompanying Roles vocabulary + prof:hasRole role:Specification ; + + # this profile resource's actual file + prof:hasArtifact ; + + # this profile is inherited from Workflow Run profile + prof:isInheritedFrom ; + ] ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "five-safes-crate" ; +. From 7ab6b6de00ead4c16aeed7729eafd32efdb6ecad Mon Sep 17 00:00:00 2001 From: Douglas Lowe <10961945+douglowe@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:10:16 +0100 Subject: [PATCH 02/18] 5-safes crate first req --- .../must/0_root_data_entity_metadata.ttl | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 rocrate_validator/profiles/five-safes-crate/must/0_root_data_entity_metadata.ttl diff --git a/rocrate_validator/profiles/five-safes-crate/must/0_root_data_entity_metadata.ttl b/rocrate_validator/profiles/five-safes-crate/must/0_root_data_entity_metadata.ttl new file mode 100644 index 00000000..2cac720c --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/must/0_root_data_entity_metadata.ttl @@ -0,0 +1,37 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@prefix ro: <./> . +@prefix ro-crate: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +ro-crate:RootDataEntityRequiredProperties + a sh:NodeShape ; + sh:name "Five Safes Crate Root Data Entity REQUIRED properties" ; + sh:description "The Root Data Entity MUST have a `funding`" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity: `funding` property" ; + sh:description """Check if the Root Data Entity includes a `funding` property (as specified by schema.org).""" ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path schema_org:funding; + sh:message "The Root Data Entity MUST have a `funding` property (as specified by schema.org)" ; + ] . From 5f45864b59df58902fd163212a70cbc971e53876 Mon Sep 17 00:00:00 2001 From: Douglas Lowe <10961945+douglowe@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:51:20 +0100 Subject: [PATCH 03/18] adding 5-safe ro-crate example json --- .../ro-crate-metadata.json | 160 ++++++ .../ro-crate-metadata.json | 392 +++++++++++++++ .../ro-crate-metadata.json | 461 ++++++++++++++++++ 3 files changed, 1013 insertions(+) create mode 100644 tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json create mode 100644 tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json create mode 100644 tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json diff --git a/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json new file mode 100644 index 00000000..b8a84271 --- /dev/null +++ b/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json @@ -0,0 +1,160 @@ +{ + "@context": "https://w3id.org/ro/crate/1.2-DRAFT/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "about": { + "@id": "./" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.2-DRAFT" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "hasPart": [ + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "input1.txt" + } + ], + "mainEntity": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "mentions": { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538" + }, + "sourceOrganization": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": "Profile", + "name": "Five Safes RO-Crate profile" + }, + { + "@id": "https://workflowhub.eu/workflows/289?version=1", + "@type": "Dataset", + "name": "CWL Protein MD Setup tutorial with mutations", + "conformsTo": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + }, + "distribution": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + } + }, + { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1", + "@type": "DataDownload", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + }, + "encodingFormat": "application/zip" + }, + { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538", + "@type": "CreateAction", + "actionStatus": "http://schema.org/PotentialActionStatus", + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "instrument": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "name": "Execute query 12389 on workflow ", + "object": [ + { + "@id": "input1.txt" + }, + { + "@id": "#enableFastMode" + } + ] + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://ror.org/027m9bs27" + }, + "memberOf": [ + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + ] + }, + { + "@id": "https://ror.org/027m9bs27", + "@type": "Organization", + "name": "The University of Manchester" + }, + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70", + "@type": "Project", + "name": "Investigation of cancer (TRE72 project 81)", + "identifier": [ + { + "@id": "_:localid:tre72:project81" + } + ], + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=10038961" + }, + "member": [ + { + "@id": "https://ror.org/027m9bs27" + }, + { + "@id": "https://ror.org/01ee9ar58" + } + ] + }, + { + "@id": "_:localid:tre72:project81", + "@type": "PropertyValue", + "name": "tre72", + "value": "project81" + }, + { + "@id": "https://gtr.ukri.org/projects?ref=10038961", + "@type": "Grant", + "name": "EOSC4Cancer" + }, + { + "@id": "input1.txt", + "@type": "File", + "name": "input1", + "exampleOfWork": { + "@id": "#sequence" + } + }, + { + "@id": "#enableFastMode", + "@type": "PropertyValue", + "name": "--fast-mode", + "value": "True", + "exampleOfWork": { + "@id": "#fast" + } + }, + { + "@id": "#sequence", + "@type": "FormalParameter", + "name": "input-sequence" + }, + { + "@id": "#fast", + "@type": "FormalParameter", + "name": "fast-mode" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json new file mode 100644 index 00000000..3b43f636 --- /dev/null +++ b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json @@ -0,0 +1,392 @@ +{ + "@context": "https://w3id.org/ro/crate/1.2-DRAFT/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "about": { + "@id": "./" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.2-DRAFT" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "hasPart": [ + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "input1.txt" + }, + { + "@id": "workflow/289/" + } + ], + "mainEntity": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "mentions": [ + { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538" + }, + { + "@id": "#check-f33fe90c-0c22-4c72-b299-de509028410e" + }, + { + "@id": "#validate-1146f640-819e-4c86-b029-b763a0040896" + }, + { + "@id": "#download-8b51bf57-6b29-44da-b24b-638c8df91639" + }, + { + "@id": "#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0" + }, + { + "@id": "#disclosure-b16c1f0a-ae7f-4582-9b28-7d9df3313e27" + }, + { + "@id": "#bagit-ce785c0b-c988-4043-8cbd-1489dcebc14f" + } + ], + "sourceOrganization": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + }, + "publisher": { + "@id": "https://tre72.example.com/" + }, + "licence": { + "@id": "http://spdx.org/licenses/CC-BY-4.0" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": "Profile", + "name": "Five Safes RO-Crate profile" + }, + { + "@id": "https://spdx.org/licenses/CC-BY-4.0", + "@type": "CreativeWork", + "name": "Creative Commons Attribution 4.0 International", + "identifier": "CC-BY-4.0" + }, + { + "@id": "https://workflowhub.eu/workflows/289?version=1", + "@type": "Dataset", + "name": "CWL Protein MD Setup tutorial with mutations", + "conformsTo": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + }, + "distribution": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + } + }, + { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1", + "@type": "DataDownload", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + }, + "encodingFormat": "application/zip" + }, + { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538", + "@type": "CreateAction", + "actionStatus": "http://schema.org/CompleteActionStatus", + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "instrument": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "name": "Execute query 12389 on workflow ", + "object": [ + { + "@id": "input1.txt" + }, + { + "@id": "#enableFastMode" + } + ], + "result": [ + { + "@id": "outputs/table.csv" + }, + { + "@id": "outputs/diagrams/" + }, + { + "@id": "urn:uuid:07b81e0f-7ac4-5428-9940-878b241e2397" + } + ] + }, + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70", + "@type": "Project", + "name": "Investigation of cancer (TRE72 project 81)", + "identifier": [ + { + "@id": "_:localid:tre72:project81" + } + ], + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=10038961" + }, + "member": [ + { + "@id": "https://ror.org/027m9bs27" + }, + { + "@id": "https://ror.org/01ee9ar58" + } + ] + }, + { + "@id": "_:localid:tre72:project81", + "@type": "PropertyValue", + "name": "tre72", + "value": "project81" + }, + { + "@id": "input1.txt", + "@type": "File", + "name": "input1", + "exampleOfWork": { + "@id": "#sequence" + } + }, + { + "@id": "#enableFastMode", + "@type": "PropertyValue", + "name": "--fast-mode", + "value": "True", + "exampleOfWork": { + "@id": "#fast" + } + }, + { + "@id": "#sequence", + "@type": "FormalParameter", + "name": "input-sequence" + }, + { + "@id": "#fast", + "@type": "FormalParameter", + "name": "fast-mode" + }, + { + "@id": "outputs/qa.csv", + "@type": "File", + "encodingFormat": "text/csv", + "name": "Tabular listing of quality assessment" + }, + { + "@id": "outputs/diagrams/", + "@type": "Dataset", + "name": "Diagrams of regions of interest" + }, + { + "@id": "workflow/289/", + "sameAs": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "@type": "Dataset", + "name": "CWL Protein MD Setup tutorial with mutations", + "conformsTo": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + }, + "distribution": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + } + }, + { + "@id": "#check-f33fe90c-0c22-4c72-b299-de509028410e", + "type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#CheckValue" + }, + "name": "BagIt checksum of Crate: OK", + "endTime": "2023-04-18T12:11:45+01:00", + "object": { + "@id": "./" + }, + "instrument": { + "@id": "https://www.iana.org/assignments/named-information#sha-512" + }, + "agent": { + "@id": "#validator-a4a66c63-2fe0-4c57-830d-268a40718313" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#validate-1146f640-819e-4c86-b029-b763a0040896", + "type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#ValidationCheck" + }, + "name": "Validation against Five Safes RO-Crate profile: approved", + "startTime": "2023-04-18T12:11:46+01:00", + "endTime": "2023-04-18T12:11:49+01:00", + "object": { + "@id": "./" + }, + "instrument": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "agent": { + "@id": "#validator-a4a66c63-2fe0-4c57-830d-268a40718313" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#download-8b51bf57-6b29-44da-b24b-638c8df91639", + "type": "DownloadAction", + "name": "Downloaded workflow RO-Crate via proxy", + "startTime": "2023-04-18T12:11:50+01:00", + "endTime": "2023-04-18T12:11:52+01:00", + "object": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + }, + "result": { + "@id": "workflow/289/" + }, + "agent": { + "@id": "http://proxy.example.com/" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0", + "type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#SignOff" + }, + "name": "Sign-off of execution according to Agreement policy: approved", + "endTime": "2023-04-19T17:15:12+01:00", + "object": [ + { + "@id": "./" + }, + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + ], + "instrument": { + "@id": "https://tre72.example.com/agreement-policy/81" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#disclosure-b16c1f0a-ae7f-4582-9b28-7d9df3313e27", + "type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#DisclosureCheck" + }, + "name": "Disclosure check of workflow results: approved", + "endTime": "2023-04-25T16:00:00+01:00", + "object": { + "@id": "./" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#bagit-ce785c0b-c988-4043-8cbd-1489dcebc14f", + "type": "UpdateAction", + "startTime": "2023-04-29T12:12:25+01:00", + "additionalType": { + "@id": "https://w3id.org/shp#GenerateCheckValue" + }, + "name": "BagIt manifests of Crate updated", + "object": { + "@id": "./" + }, + "instrument": { + "@id": "https://www.iana.org/assignments/named-information#sha-512" + }, + "agent": { + "@id": "#validator-a4a66c63-2fe0-4c57-830d-268a40718313" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "urn:uuid:07b81e0f-7ac4-5428-9940-878b241e2397", + "@type": "DigitalDocument", + "encodingFormat": "text/csv", + "name": "Patient measurement 07b81e0f-7ac4-5428-9940-878b241e2397", + "hasDigitalDocumentPermission": { + "@id": "#permissions-07b81e0f" + } + }, + { + "@id": "#permissions-07b81e0f", + "@type": "DigitalDocumentPermission", + "permissionType": "http://schema.org/ReadPermission", + "grantee": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://ror.org/027m9bs27" + }, + "memberOf": [ + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + ] + }, + { + "@id": "https://ror.org/027m9bs27", + "@type": "Organization", + "name": "The University of Manchester" + }, + { + "@id": "https://gtr.ukri.org/projects?ref=10038961", + "@type": "Grant", + "name": "EOSC4Cancer" + }, + { + "@id": "https://tre72.example.com/", + "@type": "Organization", + "name": "TRE 72 trusted research environment at The University of Manchester", + "parentOrganization": { + "@id": "https://ror.org/027m9bs27" + } + }, + { + "@id": "https://tre72.example.com/#crate-validator", + "@type": "SoftwareApplication", + "name": "RO-Crate validator at TRE72", + "provider": { + "@id": "https://tre72.example.com/" + } + }, + { + "@id": "https://tre72.example.com/agreement-policy/81", + "@type": "CreativeWork", + "name": "Agreement policy for TRE72 for project 81" + }, + { + "@id": "https://www.iana.org/assignments/named-information#sha-512", + "@type": "DefinedTerm", + "name": "sha-512 algorithm" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json new file mode 100644 index 00000000..5ca7ea11 --- /dev/null +++ b/tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json @@ -0,0 +1,461 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "about": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "licence": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": [ + "Dataset", + "Profile" + ], + "name": "Five Safes RO-Crate profile", + "cite-as": "https://w3id.org/5s-crate/0.4", + "version": "0.4", + "datePublished": "2023-05-15T10:32:00Z", + "licence": { + "@id": "http://spdx.org/licenses/MIT" + }, + "copyrightHolder": { + "@id": "https://ror.org/027m9bs27" + }, + "copyrightYear": 2023, + "hasPart": [ + { + "@id": "https://trefx.uk/5s-crate/0.4/index.html" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request.bagit.zip" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result.bagit.zip" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/" + }, + { + "@id": "http://schema.org/PotentialActionStatus" + }, + { + "@id": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "http://schema.org/ActiveActionStatus" + }, + { + "@id": "http://schema.org/FailedActionStatus" + }, + { + "@id": "http://schema.org/CreateAction" + }, + { + "@id": "http://schema.org/AssessAction" + }, + { + "@id": "http://schema.org/DownloadAction" + }, + { + "@id": "http://schema.org/UpdateAction" + }, + { + "@id": "http://schema.org/grantee" + }, + { + "@id": "http://schema.org/DigitalDocument" + }, + { + "@id": "http://schema.org/DigitalDocumentPermission" + }, + { + "@id": "http://schema.org/hasDigitalDocumentPermission" + }, + { + "@id": "http://schema.org/ReadPermission" + }, + { + "@id": "https://bioschemas.org/FormalParameter" + }, + { + "@id": "https://w3id.org/shp#CheckValue" + }, + { + "@id": "https://w3id.org/shp#ValidationCheck" + }, + { + "@id": "https://w3id.org/shp#SignOff" + }, + { + "@id": "https://w3id.org/shp#DisclosureCheck" + }, + { + "@id": "https://w3id.org/shp#GenerateCheckValue" + } + ], + "hasResource": [ + { + "@id": "#hasSpecification" + }, + { + "@id": "#hasExampleRequest" + }, + { + "@id": "#hasExampleResult" + }, + { + "@id": "#usesSHP" + } + ], + "publisher": { + "@id": "https://trefx.uk/" + }, + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=MC_PC_23007" + }, + "author": [ + { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + { + "@id": "https://orcid.org/0009-0003-2419-1964" + } + ] + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/index.html", + "@type": "CreativeWork", + "name": "Five Safes RO-Crate profile (specification)" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/", + "@type": "Dataset", + "name": "Example Crate in Request state", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "subjectOf": [ + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-metadata.json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-preview.html" + } + ], + "distribution": { + "@id": "https://trefx.uk/5s-crate/0.4/example-request.bagit.zip" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/", + "@type": "Dataset", + "name": "Example Crate in Request state", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "subjectOf": [ + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-metadata.json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-preview.html" + } + ], + "distribution": { + "@id": "https://trefx.uk/5s-crate/0.4/example-result.bagit.zip" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result.bagit.zip", + "@type": "DataDownload", + "name": "example-result.bagit.zip", + "description": "Example Result following the 5s-crate profile, archived as BagIt ZIP", + "encodingFormat": "application/zip", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request.bagit.zip", + "@type": "DataDownload", + "name": "example-request.bagit.zip", + "description": "Example Request following the 5s-crate profile, archived as BagIt ZIP", + "encodingFormat": "application/zip", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-metadata.json", + "@type": "CreativeWork", + "encodingFormat": "application/ld+json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-preview.html", + "@type": "CreativeWork", + "encodingFormat": "text/html" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-metadata.json", + "@type": "CreativeWork", + "encodingFormat": "application/ld+json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-preview.html", + "@type": "CreativeWork", + "encodingFormat": "text/html" + }, + { + "@id": "https://bioschemas.org/FormalParameter", + "@type": "DefinedTerm" + }, + { + "@id": "https://w3id.org/shp", + "@type": "DefinedTermSet", + "name": "The Safe Haven Provenance (SHP) Ontology", + "version": "0.1" + }, + { + "@id": "https://w3id.org/shp#CheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#ValidationCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#SignOff", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#DisclosureCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#GenerateCheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#CheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#ValidationCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#SignOff", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#DisclosureCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#GenerateCheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://www.iana.org/assignments/named-information#sha-512", + "@type": "DefinedTerm", + "name": "sha-512 algorithm" + }, + { + "@id": "#usesSHP", + "name": "uses vocabulary SHP", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/vocabulary" + }, + "hasArtifact": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "#hasSpecification", + "@type": "ResourceDescriptor", + "name": "has specification", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/specification" + }, + "hasArtifact": { + "@id": "https://trefx.uk/5s-crate/0.4/index.html" + } + }, + { + "@id": "#hasExampleRequest", + "@type": "ResourceDescriptor", + "name": "has example: Request", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/example" + }, + "hasArtifact": { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/" + } + }, + { + "@id": "#hasExampleResult", + "@type": "ResourceDescriptor", + "name": "has example: Result", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/example" + }, + "hasArtifact": { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/" + } + }, + + { + "@id": "http://www.w3.org/ns/dx/prof/role/example", + "@type": [ + "DefinedTerm", + "ResourceRole" + ], + "name": "Example", + "description": "Sample instance data conforming to the profile" + }, + { + "@id": "http://www.w3.org/ns/dx/prof/role/specification", + "@type": [ + "DefinedTerm", + "ResourceRole" + ], + "name": "Specification", + "description": "Defining the profile in human-readable form" + }, + { + "@id": "http://www.w3.org/ns/dx/prof/role/vocabulary", + "@type": [ + "DefinedTerm", + "ResourceRole" + ], + "name": "Vocabulary", + "description": "Defines terms used in the profile specification" + }, + { + "@id": "http://spdx.org/licenses/CC0-1.0", + "@type": "CreativeWork", + "name": "Creative Commons Zero v1.0 Universal", + "identifier": "CC0-1.0", + "url": "https://creativecommons.org/publicdomain/zero/1.0/" + }, + { + "@id": "http://spdx.org/licenses/MIT", + "@type": "CreativeWork", + "name": "MIT Licence", + "identifier": "MIT" + }, + { + "@id": "https://gtr.ukri.org/projects?ref=MC_PC_23007", + "@type": "Grant", + "identifier": { + "@id": "_:ukri:MC_PC_23007" + }, + "name": "DARE-FX: Delivering a federated network of TREs to enable safe analytics", + "description": "", + "funder": { + "@id": "https://ror.org/001aqnf71" + }, + "sponsor": { + "@id": "https://dareuk.org.uk/" + } + }, + { + "@id": "https://dareuk.org.uk/", + "@type": "FundingScheme", + "name": "DARE UK", + "funder": { + "@id": "https://ror.org/001aqnf71" + } + }, + { + "@id": "_:ukri:MC_PC_23007", + "@type": "PropertyValue", + "name": "UKRI", + "value": "MC_PC_23007" + }, + { + "@id": "https://trefx.uk/", + "@type": "ResearchProject", + "name": "TRE-FX", + "description": "Delivering a federated network of TREs to enable safe analytics", + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=MC_PC_23007" + } + }, + { + "@id": "https://ror.org/001aqnf71", + "@type": "FundingAgency", + "name": "UK Research and Innovation", + "alternateName": "UKRI", + "url": "https://www.ukri.org/" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://ror.org/027m9bs27" + }, + "memberOf": { + "@id": "https://trefx.uk/" + } + }, + { + "@id": "https://ror.org/027m9bs27", + "@type": "CollegeOrUniversity", + "name": "The University of Manchester", + "url": "https://www.manchester.ac.uk/" + }, + { + "@id": "https://orcid.org/0009-0003-2419-1964", + "@type": "Person", + "name": "Stuart Wheater", + "memberOf": { + "@id": "https://trefx.uk/" + } + } + ] +} \ No newline at end of file From cf2b8088f41a09ff82a5d036d6f0332ef40173ae Mon Sep 17 00:00:00 2001 From: Douglas Lowe <10961945+douglowe@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:54:14 +0100 Subject: [PATCH 04/18] tests for 5-safe crate request and result profiles --- .../five-safes-crate/test_valid_5src.py | 44 +++++++++++++++++++ tests/ro_crates.py | 8 ++++ 2 files changed, 52 insertions(+) create mode 100644 tests/integration/profiles/five-safes-crate/test_valid_5src.py diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py new file mode 100644 index 00000000..1cc503b0 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -0,0 +1,44 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from rocrate_validator.models import Severity +from tests.conftest import SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +def test_valid_five_safes_crate_request_required(): + """Test a valid Workflow Run Crate.""" + do_entity_test( + ValidROC().five_safes_crate_request, + Severity.REQUIRED, + True, + profile_identifier="five-safes-crate", + skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER] + ) + +def test_valid_five_safes_crate_result_required(): + """Test a valid Workflow Run Crate.""" + do_entity_test( + ValidROC().five_safes_crate_result, + Severity.REQUIRED, + True, + profile_identifier="five-safes-crate", + skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER] + ) diff --git a/tests/ro_crates.py b/tests/ro_crates.py index 1c3d03a9..6ecc64ab 100644 --- a/tests/ro_crates.py +++ b/tests/ro_crates.py @@ -91,6 +91,14 @@ def provenance_run_crate(self) -> Path: def multi_profile_crate(self) -> Path: return VALID_CRATES_DATA_PATH / "multi-profile-crate" + @property + def five_safes_crate_request(self) -> Path: + return VALID_CRATES_DATA_PATH / "five-safes-crate-request" + + @property + def five_safes_crate_result(self) -> Path: + return VALID_CRATES_DATA_PATH / "five-safes-crate-result" + class InvalidFileDescriptor: From cab85790f4548965d0a8b65699aa7bf76d7e8475 Mon Sep 17 00:00:00 2001 From: Douglas Lowe <10961945+douglowe@users.noreply.github.com> Date: Sat, 20 Sep 2025 15:12:16 +0100 Subject: [PATCH 05/18] five-safes request and result conform to rocrate-1.1 --- .../ro-crate-metadata.json | 8 +++-- .../ro-crate-metadata.json | 29 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json index b8a84271..9fb0d7bf 100644 --- a/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json +++ b/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json @@ -1,5 +1,5 @@ { - "@context": "https://w3id.org/ro/crate/1.2-DRAFT/context", + "@context": "https://w3id.org/ro/crate/1.1/context", "@graph": [ { "@type": "CreativeWork", @@ -8,12 +8,16 @@ "@id": "./" }, "conformsTo": { - "@id": "https://w3id.org/ro/crate/1.2-DRAFT" + "@id": "https://w3id.org/ro/crate/1.1" } }, { "@id": "./", "@type": "Dataset", + "name": "5-Safe RO-Crate Request", + "description": "example 5-Safe RO-Crate request metadata for testing", + "license": "Apache-2.0", + "datePublished": "2025-09-20T14:38:00+00:00", "conformsTo": { "@id": "https://w3id.org/5s-crate/0.4" }, diff --git a/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json index 3b43f636..a6475f63 100644 --- a/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json +++ b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json @@ -1,5 +1,5 @@ { - "@context": "https://w3id.org/ro/crate/1.2-DRAFT/context", + "@context": "https://w3id.org/ro/crate/1.1/context", "@graph": [ { "@type": "CreativeWork", @@ -8,12 +8,15 @@ "@id": "./" }, "conformsTo": { - "@id": "https://w3id.org/ro/crate/1.2-DRAFT" + "@id": "https://w3id.org/ro/crate/1.1" } }, { "@id": "./", "@type": "Dataset", + "name": "5-Safe RO-Crate Result", + "description": "example 5-Safe RO-Crate result metadata for testing", + "datePublished": "2025-09-20T14:45:00+00:00", "conformsTo": { "@id": "https://w3id.org/5s-crate/0.4" }, @@ -24,6 +27,12 @@ { "@id": "input1.txt" }, + { + "@id": "outputs/qa.csv" + }, + { + "@id": "outputs/diagrams/" + }, { "@id": "workflow/289/" } @@ -60,7 +69,7 @@ "publisher": { "@id": "https://tre72.example.com/" }, - "licence": { + "license": { "@id": "http://spdx.org/licenses/CC-BY-4.0" } }, @@ -115,7 +124,7 @@ ], "result": [ { - "@id": "outputs/table.csv" + "@id": "outputs/qa.csv" }, { "@id": "outputs/diagrams/" @@ -206,7 +215,7 @@ }, { "@id": "#check-f33fe90c-0c22-4c72-b299-de509028410e", - "type": "AssessAction", + "@type": "AssessAction", "additionalType": { "@id": "https://w3id.org/shp#CheckValue" }, @@ -225,7 +234,7 @@ }, { "@id": "#validate-1146f640-819e-4c86-b029-b763a0040896", - "type": "AssessAction", + "@type": "AssessAction", "additionalType": { "@id": "https://w3id.org/shp#ValidationCheck" }, @@ -245,7 +254,7 @@ }, { "@id": "#download-8b51bf57-6b29-44da-b24b-638c8df91639", - "type": "DownloadAction", + "@type": "DownloadAction", "name": "Downloaded workflow RO-Crate via proxy", "startTime": "2023-04-18T12:11:50+01:00", "endTime": "2023-04-18T12:11:52+01:00", @@ -262,7 +271,7 @@ }, { "@id": "#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0", - "type": "AssessAction", + "@type": "AssessAction", "additionalType": { "@id": "https://w3id.org/shp#SignOff" }, @@ -289,7 +298,7 @@ }, { "@id": "#disclosure-b16c1f0a-ae7f-4582-9b28-7d9df3313e27", - "type": "AssessAction", + "@type": "AssessAction", "additionalType": { "@id": "https://w3id.org/shp#DisclosureCheck" }, @@ -305,7 +314,7 @@ }, { "@id": "#bagit-ce785c0b-c988-4043-8cbd-1489dcebc14f", - "type": "UpdateAction", + "@type": "UpdateAction", "startTime": "2023-04-29T12:12:25+01:00", "additionalType": { "@id": "https://w3id.org/shp#GenerateCheckValue" From 599ffd0e8a1f0d7674712e83104819049dc737c1 Mon Sep 17 00:00:00 2001 From: Douglas Lowe <10961945+douglowe@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:48:18 +0100 Subject: [PATCH 06/18] 5S-crate funder test added --- .../five-safes-crate/must/0_funder.ttl | 35 +++++++++++++++++++ ...ta.ttl => 1_root_data_entity_metadata.ttl} | 16 +++++---- 2 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 rocrate_validator/profiles/five-safes-crate/must/0_funder.ttl rename rocrate_validator/profiles/five-safes-crate/must/{0_root_data_entity_metadata.ttl => 1_root_data_entity_metadata.ttl} (64%) diff --git a/rocrate_validator/profiles/five-safes-crate/must/0_funder.ttl b/rocrate_validator/profiles/five-safes-crate/must/0_funder.ttl new file mode 100644 index 00000000..247faa90 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/must/0_funder.ttl @@ -0,0 +1,35 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@prefix ro: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix schema_org: . +@prefix sh: . +@prefix bioschemas: . + +five-safes-crate:FundingBody a sh:NodeShape ; + sh:name "Funding body Project" ; + sh:description "Project which is funding this work" ; + sh:targetClass schema_org:Project ; + sh:property [ + a sh:PropertyShape ; + sh:name "Project Name" ; + sh:description """Check if the Project Entity `name` (as specified by schema.org) + to clearly identify the dataset and distinguish it from other projects.""" ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path schema_org:name; + sh:message "The Project Entity MUST have a `name` property (as specified by schema.org)" ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/must/0_root_data_entity_metadata.ttl b/rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity_metadata.ttl similarity index 64% rename from rocrate_validator/profiles/five-safes-crate/must/0_root_data_entity_metadata.ttl rename to rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity_metadata.ttl index 2cac720c..645f5dd0 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/0_root_data_entity_metadata.ttl +++ b/rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity_metadata.ttl @@ -14,6 +14,7 @@ @prefix ro: <./> . @prefix ro-crate: . +@prefix five-safes-crate: . @prefix rdf: . @prefix schema_org: . @prefix sh: . @@ -21,17 +22,18 @@ @prefix xsd: . -ro-crate:RootDataEntityRequiredProperties +five-safes-crate:RootDataEntityRequiredProperties a sh:NodeShape ; sh:name "Five Safes Crate Root Data Entity REQUIRED properties" ; - sh:description "The Root Data Entity MUST have a `funding`" ; + sh:description "The Root Data Entity MUST have a `sourceOrganisation`" ; sh:targetClass ro-crate:RootDataEntity ; sh:property [ a sh:PropertyShape ; - sh:name "Root Data Entity: `funding` property" ; - sh:description """Check if the Root Data Entity includes a `funding` property (as specified by schema.org).""" ; + sh:name "Root Data Entity: `sourceOrganization` property" ; + sh:description """Check if the Root Data Entity includes a `sourceOrganization` (as specified by schema.org).""" ; + sh:path schema_org:sourceOrganization; sh:minCount 1 ; - sh:nodeKind sh:Literal ; - sh:path schema_org:funding; - sh:message "The Root Data Entity MUST have a `funding` property (as specified by schema.org)" ; + sh:class ro-crate:ContextualEntity ; + sh:message """The Root Data Entity MUST have a `sourceOrganization` property (as specified by schema.org). + SHOULD link to a Contextual Entity in the RO-Crate Metadata File with a name.""" ; ] . From 2cc55b9208b57b4969a0cdcf6a4c3aeb7a3fd649 Mon Sep 17 00:00:00 2001 From: Douglas Lowe <10961945+douglowe@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:44:28 +0100 Subject: [PATCH 07/18] renumber funder test for now --- .../profiles/five-safes-crate/must/{0_funder.ttl => 1_funder.ttl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rocrate_validator/profiles/five-safes-crate/must/{0_funder.ttl => 1_funder.ttl} (100%) diff --git a/rocrate_validator/profiles/five-safes-crate/must/0_funder.ttl b/rocrate_validator/profiles/five-safes-crate/must/1_funder.ttl similarity index 100% rename from rocrate_validator/profiles/five-safes-crate/must/0_funder.ttl rename to rocrate_validator/profiles/five-safes-crate/must/1_funder.ttl From 47023b1701f9850e314e4e06b95828acc2cb18a9 Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Thu, 25 Sep 2025 13:25:44 +0100 Subject: [PATCH 08/18] Added chekts for rules about responsible Agent. --- .../five-safes-crate/must/2_agent_entity.ttl | 107 ++++++++++++++++++ .../should/2_agent_entity.ttl | 26 +++++ 2 files changed, 133 insertions(+) create mode 100644 rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl diff --git a/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl new file mode 100644 index 00000000..30805ceb --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl @@ -0,0 +1,107 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@prefix ro: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +# Any CreateAction entity must have one (and only one) agent +five-safes-crate:CreateActionHasAgent + a sh:NodeShape ; + sh:description "CreateAction MUST have an agent property." ; + sh:targetClass schema:CreateAction ; + sh:property [ + sh:path schema:agent ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:message "CreateAction MUST have an agent property." ; + ] . + + +# The agent of a CreateAction must be a Person +five-safes-crate:CreateActionAgentIsPerson + a sh:NodeShape ; + sh:description "The agent of a CreateAction entity MUST be a Person." ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a schema:CreateAction ; + schema:agent ?this . + } + """ + ] ; + sh:class schema:Person ; + sh:severity sh:Violation ; + sh:message "The agent of a CreateAction entity MUST be a Person." . + + +# The affiliation (if any) of a CreateAction's agent MUST be an Organization +five-safes-crate:AgentAffiliationIsOrganization + a sh:NodeShape ; + sh:description "The affiliation (if any) of a CreateAction's agent MUST be an Organization" ; + sh:severity sh:Violation ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a schema:CreateAction ; + schema:agent ?this . + ?this a schema:Person . + ?this schema:affiliation ?aff . + } + """ + ] ; + sh:property [ + sh:path schema:affiliation ; + sh:class schema:Organization ; + sh:message "The affiliation (if any) of a CreateAction's agent MUST be an Organization" ; + ] . + + +# All schema:Organization instances: @id MUST be a PID permalink (ROR / ISNI / Wikidata) +five-safes-crate:OrganizationIdIsPIDPermalink + a sh:NodeShape ; + sh:name "Organizations value restriction" ; + sh:description "An Organization `@id` must be a PID permalink (ROR, ISNI, or Wikidata)." ; + + # Target the CLASS node; values are all subjects with rdf:type schema:Organization + sh:targetNode schema:Organization ; + + sh:property [ + a sh:PropertyShape ; + sh:name "Organization URI value" ; + sh:description "Check that the Organization entity URI (@id) is a PID permalink." ; + + # From the class node, walk inverse rdf:type to all instances + sh:path [ sh:inversePath rdf:type ] ; + + # Require at least one instance (prevents empty graphs from 'passing') + sh:minCount 1 ; + + # Regex over the string form of the instance IRI + sh:pattern "^(?:https://(?:ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/\\d{15}[\\dX]|(?:www\\.)?wikidata\\.org/entity/Q\\d+))$" ; + sh:flags "i" ; + + sh:message "Organization @id MUST be a PID permalink (ROR/ISNI/Wikidata)." ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl new file mode 100644 index 00000000..1e9f6658 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl @@ -0,0 +1,26 @@ +@prefix ro: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +# Person who is the agent of a CreateAction SHOULD have an affiliation +five-safes-crate:PersonAgentHasAffiliation + a sh:NodeShape ; + sh:name "Organizations value restriction" ; + sh:description "An Organization `@id` must be a permalink" ; + sh:targetClass schema:Organization ; + sh:property [ + a sh:PropertyShape ; + sh:name "Organization URI value" ; + sh:description "Check if the Organization Entity URI is a permalink" ; + sh:path [ sh:inversePath rdf:type ] ; + sh:minCount 1 ; + sh:message """An Organization URI MUST be a permalink""" ; + sh:pattern "^(https://(ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/[0-9]{15}[0-9X]|(www\\.)?wikidata\\.org/entity/Q[0-9]+))$" ; +sh:flags "i" ; + ] . \ No newline at end of file From c7d608498bfd5ac29e02fbb45dae8e8b1c2ec922 Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Thu, 25 Sep 2025 14:10:54 +0100 Subject: [PATCH 09/18] Ammended test checking that agent SHOULD have an affiliation. --- .../five-safes-crate/must/2_agent_entity.ttl | 28 ---------- .../should/2_agent_entity.ttl | 55 +++++++++++++++---- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl index 30805ceb..684674f0 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl +++ b/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl @@ -77,31 +77,3 @@ five-safes-crate:AgentAffiliationIsOrganization sh:class schema:Organization ; sh:message "The affiliation (if any) of a CreateAction's agent MUST be an Organization" ; ] . - - -# All schema:Organization instances: @id MUST be a PID permalink (ROR / ISNI / Wikidata) -five-safes-crate:OrganizationIdIsPIDPermalink - a sh:NodeShape ; - sh:name "Organizations value restriction" ; - sh:description "An Organization `@id` must be a PID permalink (ROR, ISNI, or Wikidata)." ; - - # Target the CLASS node; values are all subjects with rdf:type schema:Organization - sh:targetNode schema:Organization ; - - sh:property [ - a sh:PropertyShape ; - sh:name "Organization URI value" ; - sh:description "Check that the Organization entity URI (@id) is a PID permalink." ; - - # From the class node, walk inverse rdf:type to all instances - sh:path [ sh:inversePath rdf:type ] ; - - # Require at least one instance (prevents empty graphs from 'passing') - sh:minCount 1 ; - - # Regex over the string form of the instance IRI - sh:pattern "^(?:https://(?:ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/\\d{15}[\\dX]|(?:www\\.)?wikidata\\.org/entity/Q\\d+))$" ; - sh:flags "i" ; - - sh:message "Organization @id MUST be a PID permalink (ROR/ISNI/Wikidata)." ; - ] . diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl index 1e9f6658..86d97f72 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl @@ -11,16 +11,49 @@ # Person who is the agent of a CreateAction SHOULD have an affiliation five-safes-crate:PersonAgentHasAffiliation a sh:NodeShape ; - sh:name "Organizations value restriction" ; - sh:description "An Organization `@id` must be a permalink" ; - sh:targetClass schema:Organization ; + sh:mame "Agent's affiliation" ; + sh:description "The agent of a CreateAction entity SHOULD have an affiliation" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a schema:CreateAction ; + schema:agent ?this . + ?this a schema:Person . + } + """ + ] ; sh:property [ - a sh:PropertyShape ; - sh:name "Organization URI value" ; - sh:description "Check if the Organization Entity URI is a permalink" ; - sh:path [ sh:inversePath rdf:type ] ; + sh:path schema:affiliation ; sh:minCount 1 ; - sh:message """An Organization URI MUST be a permalink""" ; - sh:pattern "^(https://(ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/[0-9]{15}[0-9X]|(www\\.)?wikidata\\.org/entity/Q[0-9]+))$" ; -sh:flags "i" ; - ] . \ No newline at end of file + sh:message "The agent of a CreateAction entity SHOULD have an affiliation" ; + ] . + + +# All schema:Organization instances: @id SHOULD be a PID permalink (ROR / ISNI / Wikidata) +five-safes-crate:OrganizationIdIsPIDPermalink + a sh:NodeShape ; + sh:name "Organizations value restriction" ; + sh:description "An Organization `@id` should be a PID permalink (ROR, ISNI, or Wikidata)." ; + + # Target the CLASS node; values are all subjects with rdf:type schema:Organization + sh:targetNode schema:Organization ; + + sh:property [ + a sh:PropertyShape ; + sh:name "Organization URI value" ; + sh:description "Check that the Organization entity URI (@id) is a PID permalink." ; + + # From the class node, walk inverse rdf:type to all instances + sh:path [ sh:inversePath rdf:type ] ; + + # Require at least one instance (prevents empty graphs from 'passing') + sh:minCount 1 ; + + # Regex over the string form of the instance IRI + sh:pattern "^(?:https://(?:ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/\\d{15}[\\dX]|(?:www\\.)?wikidata\\.org/entity/Q\\d+))$" ; + sh:flags "i" ; + + sh:message "Organization @id SHOULD be a PID permalink (ROR/ISNI/Wikidata)." ; + ] . \ No newline at end of file From b0f21746892c90198870eea8800c5ec6d5a683e4 Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Thu, 25 Sep 2025 14:25:31 +0100 Subject: [PATCH 10/18] Removed typo. --- .../profiles/five-safes-crate/should/2_agent_entity.ttl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl index 86d97f72..caceab2c 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl @@ -11,8 +11,9 @@ # Person who is the agent of a CreateAction SHOULD have an affiliation five-safes-crate:PersonAgentHasAffiliation a sh:NodeShape ; - sh:mame "Agent's affiliation" ; + sh:name "Agent's affiliation" ; sh:description "The agent of a CreateAction entity SHOULD have an affiliation" ; + sh:message "The agent of a CreateAction entity SHOULD have an affiliation" ; sh:target [ a sh:SPARQLTarget ; sh:prefixes ro-crate:sparqlPrefixes ; From 35fad70f0af7ebdd7f906a059502f7f7a527c968 Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Mon, 29 Sep 2025 12:47:40 +0100 Subject: [PATCH 11/18] 1) Now a CreateAction entity can have more than 1 agent. 2) Also, check on @id being a permalink is now restricted to Organizations that are affiliations of agents. --- .../five-safes-crate/must/2_agent_entity.ttl | 3 +- .../should/2_agent_entity.ttl | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl index 684674f0..f86386de 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl +++ b/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl @@ -29,8 +29,7 @@ five-safes-crate:CreateActionHasAgent sh:targetClass schema:CreateAction ; sh:property [ sh:path schema:agent ; - sh:minCount 1 ; - sh:maxCount 1 ; + sh:minCount 1 ; sh:nodeKind sh:IRI ; sh:message "CreateAction MUST have an agent property." ; ] . diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl index caceab2c..61aebc2c 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl @@ -32,29 +32,33 @@ five-safes-crate:PersonAgentHasAffiliation ] . -# All schema:Organization instances: @id SHOULD be a PID permalink (ROR / ISNI / Wikidata) -five-safes-crate:OrganizationIdIsPIDPermalink +five-safes-crate:PersonAgentOfCreateAction_AffiliationPermalinkCheck a sh:NodeShape ; - sh:name "Organizations value restriction" ; - sh:description "An Organization `@id` should be a PID permalink (ROR, ISNI, or Wikidata)." ; + sh:name "Person (agent of CreateAction) → affiliation Organization PID check" ; + sh:description "For Persons who are agents of a CreateAction, validate that any affiliation Organization has a PID permalink @id." ; - # Target the CLASS node; values are all subjects with rdf:type schema:Organization - sh:targetNode schema:Organization ; + # Target: Persons that are objects of schema:agent where the subject is a CreateAction + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this WHERE { + ?this a schema:Person . + ?action a schema:CreateAction ; + schema:agent ?this . + } + """ ; + ] ; + # If the Person has an affiliation, validate the affiliation node's IRI sh:property [ a sh:PropertyShape ; - sh:name "Organization URI value" ; - sh:description "Check that the Organization entity URI (@id) is a PID permalink." ; - - # From the class node, walk inverse rdf:type to all instances - sh:path [ sh:inversePath rdf:type ] ; - - # Require at least one instance (prevents empty graphs from 'passing') - sh:minCount 1 ; - - # Regex over the string form of the instance IRI + sh:name "affiliation Organization PID permalink" ; + sh:path schema:affiliation ; + # Optional typing requirement (keep or drop as you prefer) + sh:class schema:Organization ; + sh:nodeKind sh:IRI ; sh:pattern "^(?:https://(?:ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/\\d{15}[\\dX]|(?:www\\.)?wikidata\\.org/entity/Q\\d+))$" ; sh:flags "i" ; - - sh:message "Organization @id SHOULD be a PID permalink (ROR/ISNI/Wikidata)." ; - ] . \ No newline at end of file + sh:message "Affiliation Organization @id SHOULD be a PID permalink (ROR/ISNI/Wikidata)." ; + ] . From 5279b6fa2257c3be80fa0174b6b0e4a0d9bdfe14 Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Mon, 29 Sep 2025 16:06:31 +0100 Subject: [PATCH 12/18] Renamed files containing tests for 'requesting agent'. --- .../must/{2_agent_entity.ttl => 2_requesting_agent.ttl} | 0 .../should/{2_agent_entity.ttl => 2_requesting_agent.ttl} | 4 ++++ 2 files changed, 4 insertions(+) rename rocrate_validator/profiles/five-safes-crate/must/{2_agent_entity.ttl => 2_requesting_agent.ttl} (100%) rename rocrate_validator/profiles/five-safes-crate/should/{2_agent_entity.ttl => 2_requesting_agent.ttl} (89%) diff --git a/rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl similarity index 100% rename from rocrate_validator/profiles/five-safes-crate/must/2_agent_entity.ttl rename to rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl similarity index 89% rename from rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl rename to rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl index 61aebc2c..f16803ab 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/2_agent_entity.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl @@ -58,6 +58,10 @@ five-safes-crate:PersonAgentOfCreateAction_AffiliationPermalinkCheck # Optional typing requirement (keep or drop as you prefer) sh:class schema:Organization ; sh:nodeKind sh:IRI ; + # Match the string form of the IRI against approved PID patterns. + # - ROR: https://ror.org/0XXXXXXX## (Crockford base32 + 2 digits) + # - ISNI: https://isni.org/isni/NNNNNNNNNNNNNNNX (X allowed as checksum) + # - Wikidata: https://www.wikidata.org/entity/Q (or without www) sh:pattern "^(?:https://(?:ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/\\d{15}[\\dX]|(?:www\\.)?wikidata\\.org/entity/Q\\d+))$" ; sh:flags "i" ; sh:message "Affiliation Organization @id SHOULD be a PID permalink (ROR/ISNI/Wikidata)." ; From b27c830503d427a2511b9895fc8d2b73052591ef Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Tue, 30 Sep 2025 09:13:57 +0100 Subject: [PATCH 13/18] Refactored code --- .../must/2_requesting_agent.ttl | 78 ++++++++----------- .../should/2_requesting_agent.ttl | 62 ++++++--------- 2 files changed, 53 insertions(+), 87 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl index f86386de..26ac186f 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl +++ b/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl @@ -22,57 +22,41 @@ @prefix xsd: . -# Any CreateAction entity must have one (and only one) agent + five-safes-crate:CreateActionHasAgent - a sh:NodeShape ; - sh:description "CreateAction MUST have an agent property." ; - sh:targetClass schema:CreateAction ; - sh:property [ - sh:path schema:agent ; - sh:minCount 1 ; - sh:nodeKind sh:IRI ; - sh:message "CreateAction MUST have an agent property." ; - ] . + a sh:NodeShape ; + sh:name: "CreateAction" ; + sh:targetClass schema:CreateAction ; + sh:description "Checks that a CreateAction has an agent and that each agent is a schema:Person." ; + # CreateAction entity MUST have an agent (IRI) + sh:property [ + a sh:PropertyShape ; + sh:name "Has Agent" ; + sh:path schema:agent ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:severity sh:Violation ; + sh:message "CreateAction MUST have at least one schema:agent that is an IRI." ; + ] ; -# The agent of a CreateAction must be a Person -five-safes-crate:CreateActionAgentIsPerson - a sh:NodeShape ; - sh:description "The agent of a CreateAction entity MUST be a Person." ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT DISTINCT ?this WHERE { - ?action a schema:CreateAction ; - schema:agent ?this . - } - """ - ] ; + # The agent of a CreateAction entity MUST be a Person + sh:property [ + a sh:PropertyShape ; + sh:name "Agent is a Person" ; + sh:path schema:agent ; sh:class schema:Person ; sh:severity sh:Violation ; - sh:message "The agent of a CreateAction entity MUST be a Person." . - + sh:message "Each CreateAction agent MUST be typed as schema:Person and be an IRI." ; + ] ; -# The affiliation (if any) of a CreateAction's agent MUST be an Organization -five-safes-crate:AgentAffiliationIsOrganization - a sh:NodeShape ; - sh:description "The affiliation (if any) of a CreateAction's agent MUST be an Organization" ; + # If any agent affiliation exists, it MUST be an Organization (IRI) + sh:property [ + a sh:PropertyShape ; + sh:name "Affiliation is an Organization" ; + sh:path ( schema:agent schema:affiliation ) ; + sh:class schema:Organization ; + sh:nodeKind sh:IRI ; sh:severity sh:Violation ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT DISTINCT ?this WHERE { - ?action a schema:CreateAction ; - schema:agent ?this . - ?this a schema:Person . - ?this schema:affiliation ?aff . - } - """ - ] ; - sh:property [ - sh:path schema:affiliation ; - sh:class schema:Organization ; - sh:message "The affiliation (if any) of a CreateAction's agent MUST be an Organization" ; - ] . + sh:message "The affiliation (if any) of a CreateAction's agent MUST be a schema:Organization and be an IRI." ; + ] . \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl index f16803ab..467abc8c 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl @@ -11,9 +11,8 @@ # Person who is the agent of a CreateAction SHOULD have an affiliation five-safes-crate:PersonAgentHasAffiliation a sh:NodeShape ; - sh:name "Agent's affiliation" ; - sh:description "The agent of a CreateAction entity SHOULD have an affiliation" ; - sh:message "The agent of a CreateAction entity SHOULD have an affiliation" ; + sh:name "Agent of CreateAction" ; + sh:description "The agent of a CreateAction entity" ; sh:target [ a sh:SPARQLTarget ; sh:prefixes ro-crate:sparqlPrefixes ; @@ -21,48 +20,31 @@ five-safes-crate:PersonAgentHasAffiliation SELECT DISTINCT ?this WHERE { ?action a schema:CreateAction ; schema:agent ?this . - ?this a schema:Person . } """ ] ; + + # The agent of a CreateAction entity SHOULD have an affiliation sh:property [ + sh:name "Presence of affiliations" ; sh:path schema:affiliation ; sh:minCount 1 ; + sh:severity sh:Warning ; sh:message "The agent of a CreateAction entity SHOULD have an affiliation" ; - ] . - - -five-safes-crate:PersonAgentOfCreateAction_AffiliationPermalinkCheck - a sh:NodeShape ; - sh:name "Person (agent of CreateAction) → affiliation Organization PID check" ; - sh:description "For Persons who are agents of a CreateAction, validate that any affiliation Organization has a PID permalink @id." ; - - # Target: Persons that are objects of schema:agent where the subject is a CreateAction - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this WHERE { - ?this a schema:Person . - ?action a schema:CreateAction ; - schema:agent ?this . - } - """ ; - ] ; + ] ; - # If the Person has an affiliation, validate the affiliation node's IRI - sh:property [ - a sh:PropertyShape ; - sh:name "affiliation Organization PID permalink" ; - sh:path schema:affiliation ; - # Optional typing requirement (keep or drop as you prefer) - sh:class schema:Organization ; - sh:nodeKind sh:IRI ; - # Match the string form of the IRI against approved PID patterns. - # - ROR: https://ror.org/0XXXXXXX## (Crockford base32 + 2 digits) - # - ISNI: https://isni.org/isni/NNNNNNNNNNNNNNNX (X allowed as checksum) - # - Wikidata: https://www.wikidata.org/entity/Q (or without www) - sh:pattern "^(?:https://(?:ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}|isni\\.org/isni/\\d{15}[\\dX]|(?:www\\.)?wikidata\\.org/entity/Q\\d+))$" ; - sh:flags "i" ; - sh:message "Affiliation Organization @id SHOULD be a PID permalink (ROR/ISNI/Wikidata)." ; - ] . + # If the agent has an affiliation, validate the affiliation node's IRI + sh:property [ + a sh:PropertyShape ; + sh:name "Affiliation IRI" ; + sh:path schema:affiliation ; + sh:nodeKind sh:IRI ; + sh:or ( + [ sh:pattern "^https://ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}$" ] + [ sh:pattern "^https://isni\\.org/isni/\\d{15}[\\dX]$" ] + [ sh:pattern "^https://(?:www\\.)?wikidata\\.org/entity/Q\\d+$" ] + ) ; + sh:flags "i" ; + sh:severity sh:Warning ; + sh:message "Affiliation Organization @id SHOULD be a permalink (ROR/ISNI/Wikidata)." ; + ] . \ No newline at end of file From b3f4740ff778a41491ac69a557b4e12bede8a93e Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Wed, 8 Oct 2025 10:10:40 +0100 Subject: [PATCH 14/18] Addressed most of Alex' review points. Still need to address copyright (and possibly licence for RooDataEntity...???) --- .../profiles/five-safes-crate/must/2_requesting_agent.ttl | 3 ++- .../five-safes-crate/should/2_requesting_agent.ttl | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl index 26ac186f..1a722a9c 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl +++ b/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl @@ -25,7 +25,7 @@ five-safes-crate:CreateActionHasAgent a sh:NodeShape ; - sh:name: "CreateAction" ; + sh:name "CreateAction" ; sh:targetClass schema:CreateAction ; sh:description "Checks that a CreateAction has an agent and that each agent is a schema:Person." ; @@ -45,6 +45,7 @@ five-safes-crate:CreateActionHasAgent a sh:PropertyShape ; sh:name "Agent is a Person" ; sh:path schema:agent ; + sh:nodeKind sh:IRI ; sh:class schema:Person ; sh:severity sh:Violation ; sh:message "Each CreateAction agent MUST be typed as schema:Person and be an IRI." ; diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl index 467abc8c..5431e8ea 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl @@ -26,6 +26,7 @@ five-safes-crate:PersonAgentHasAffiliation # The agent of a CreateAction entity SHOULD have an affiliation sh:property [ + a sh:PropertyShape ; sh:name "Presence of affiliations" ; sh:path schema:affiliation ; sh:minCount 1 ; @@ -40,11 +41,10 @@ five-safes-crate:PersonAgentHasAffiliation sh:path schema:affiliation ; sh:nodeKind sh:IRI ; sh:or ( - [ sh:pattern "^https://ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}$" ] - [ sh:pattern "^https://isni\\.org/isni/\\d{15}[\\dX]$" ] - [ sh:pattern "^https://(?:www\\.)?wikidata\\.org/entity/Q\\d+$" ] + [ sh:pattern "^https://ror\\.org/0[0-9a-hjkmnp-tv-z]{6}[0-9]{2}$" ; sh:flags "i"] + [ sh:pattern "^https://isni\\.org/isni/\\d{15}[\\dX]$" ; sh:flags "i"] + [ sh:pattern "^https://(?:www\\.)?wikidata\\.org/entity/Q\\d+$" ; sh:flags "i"] ) ; - sh:flags "i" ; sh:severity sh:Warning ; sh:message "Affiliation Organization @id SHOULD be a permalink (ROR/ISNI/Wikidata)." ; ] . \ No newline at end of file From 6d352126d825c790e4b08c888cf0f19786155c9c Mon Sep 17 00:00:00 2001 From: Ettore Murabito Date: Wed, 8 Oct 2025 10:21:14 +0100 Subject: [PATCH 15/18] Addressed copyright plus licence for one of the ttl file (where it was missing). --- .../five-safes-crate/must/2_requesting_agent.ttl | 2 +- .../five-safes-crate/should/2_requesting_agent.ttl | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl index 1a722a9c..71b4fe1b 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl +++ b/rocrate_validator/profiles/five-safes-crate/must/2_requesting_agent.ttl @@ -1,4 +1,4 @@ -# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025 eScience Lab, The University of Manchester # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl index 5431e8ea..8aee0a20 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/2_requesting_agent.ttl @@ -1,3 +1,17 @@ +# Copyright (c) 2025 eScience Lab, The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + @prefix ro: <./> . @prefix ro-crate: . @prefix five-safes-crate: . From 04a6d8fb1685573d4e048e1133368e32699c5e26 Mon Sep 17 00:00:00 2001 From: Alex Hambley <33315205+alexhambley@users.noreply.github.com> Date: Thu, 9 Oct 2025 09:24:54 +0100 Subject: [PATCH 16/18] Add SHACL rule for profile conformsTo (#16) --- .../should/5_profile_conformance.ttl | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl diff --git a/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl b/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl new file mode 100644 index 00000000..d06c9685 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl @@ -0,0 +1,39 @@ +# Copyright (c) 2025 eScience Lab, The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@prefix ro: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + +# Root Dataset SHOULD declare conformsTo Five Safes profile +five-safes-crate:RootDatasetConformsToFiveSafes + a sh:NodeShape ; + sh:name "Root Dataset profile conformance" ; + sh:description "The Root Data Entity (./) SHOULD declare conformsTo the Five Safes RO-Crate profile." ; + # targets the RO-Crate Root Data Entity "./" + sh:targetNode ro: ; + sh:property [ + a sh:PropertyShape ; + sh:name "conformsTo Five Safes profile" ; + sh:description "Root Dataset SHOULD include schema:conformsTo https://w3id.org/5s-crate/0.4" ; + sh:path schema:conformsTo ; + sh:hasValue ; + sh:severity sh:Warning ; + sh:message "Profile Conformance: Root Dataset SHOULD include schema:conformsTo with @id https://w3id.org/5s-crate/0.4" ; + ] . From e039661dce9cab110672c7bda14be3b8522249d1 Mon Sep 17 00:00:00 2001 From: Alex Hambley <33315205+alexhambley@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:41:24 +0100 Subject: [PATCH 17/18] Add rules for datePublished + licence on published crates (#16) --- .../should/5_profile_conformance.ttl | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl b/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl index d06c9685..6e567177 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl @@ -37,3 +37,43 @@ five-safes-crate:RootDatasetConformsToFiveSafes sh:severity sh:Warning ; sh:message "Profile Conformance: Root Dataset SHOULD include schema:conformsTo with @id https://w3id.org/5s-crate/0.4" ; ] . + +five-safes-crate:RootDatasetDatePublishedWhenPublished + a sh:NodeShape ; + sh:name "datePublished on published crates" ; + sh:description "If the root dataset is published (has schema:publisher), it SHOULD have schema:datePublished." ; + sh:targetNode ro: ; + sh:or ( + # datePublished not required if no publisher: + [ sh:not [ sh:property [ + sh:path schema:publisher ; + sh:minCount 1 ; + ]]] + # datePublished required if publisher present: + [ sh:property [ + sh:path schema:datePublished ; + sh:minCount 1 ; + sh:message "Published crates SHOULD include schema:datePublished." ; + sh:severity sh:Warning ; + ]] + ) . + +five-safes-crate:RootDatasetLicenceWhenPublished + a sh:NodeShape ; + sh:name "licence on published crates" ; + sh:description "If the root dataset is published (has schema:publisher), it SHOULD declare a licence." ; + sh:targetNode ro: ; + sh:severity sh:Warning ; + sh:message "Profile Conformance: Published crates SHOULD include a licence (schema:license or schema:licence)." ; + sh:or ( + # license not required if no publisher: + [ sh:not [ sh:property [ + sh:path schema:publisher ; + sh:minCount 1 ; + ]]] + # license/licence required if publisher present: + [ sh:property [ + sh:path [ sh:alternativePath ( schema:license schema:licence ) ] ; + sh:minCount 1 ; + ]] + ) . \ No newline at end of file From b13ed73b6e2039af502cdf9a530113bf846d3ae9 Mon Sep 17 00:00:00 2001 From: Alex Hambley <33315205+alexhambley@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:14:56 +0000 Subject: [PATCH 18/18] Tidied up profile conformance tests --- .../should/5_profile_conformance.ttl | 104 +++++++++--------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl b/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl index 6e567177..ea2a4fd8 100644 --- a/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl +++ b/rocrate_validator/profiles/five-safes-crate/should/5_profile_conformance.ttl @@ -23,57 +23,59 @@ # Root Dataset SHOULD declare conformsTo Five Safes profile five-safes-crate:RootDatasetConformsToFiveSafes - a sh:NodeShape ; - sh:name "Root Dataset profile conformance" ; - sh:description "The Root Data Entity (./) SHOULD declare conformsTo the Five Safes RO-Crate profile." ; - # targets the RO-Crate Root Data Entity "./" - sh:targetNode ro: ; - sh:property [ - a sh:PropertyShape ; - sh:name "conformsTo Five Safes profile" ; - sh:description "Root Dataset SHOULD include schema:conformsTo https://w3id.org/5s-crate/0.4" ; - sh:path schema:conformsTo ; - sh:hasValue ; - sh:severity sh:Warning ; - sh:message "Profile Conformance: Root Dataset SHOULD include schema:conformsTo with @id https://w3id.org/5s-crate/0.4" ; - ] . + a sh:NodeShape ; + sh:name "Root Dataset Five Safes RO-Crate profile conformance" ; + sh:description "The Root Data Entity (./) SHOULD declare conformsTo the Five Safes RO-Crate profile." ; + sh:targetNode ro: ; + sh:property [ + a sh:PropertyShape ; + sh:name "conformsTo Five Safes profile" ; + sh:description "Root Dataset SHOULD include schema:conformsTo https://w3id.org/5s-crate/0.4" ; + sh:path schema:conformsTo ; + sh:qualifiedValueShape [ + sh:hasValue + ] ; + sh:qualifiedMinCount 1 ; + sh:severity sh:Warning ; + sh:message "Profile Conformance: Root Dataset SHOULD include schema:conformsTo with @id https://w3id.org/5s-crate/0.4" ; + ] . five-safes-crate:RootDatasetDatePublishedWhenPublished - a sh:NodeShape ; - sh:name "datePublished on published crates" ; - sh:description "If the root dataset is published (has schema:publisher), it SHOULD have schema:datePublished." ; - sh:targetNode ro: ; - sh:or ( - # datePublished not required if no publisher: - [ sh:not [ sh:property [ - sh:path schema:publisher ; - sh:minCount 1 ; - ]]] - # datePublished required if publisher present: - [ sh:property [ - sh:path schema:datePublished ; - sh:minCount 1 ; - sh:message "Published crates SHOULD include schema:datePublished." ; - sh:severity sh:Warning ; - ]] - ) . + a sh:NodeShape ; + sh:name "datePublished present on published crates" ; + sh:description "If the root dataset is published (has schema:publisher), it SHOULD have schema:datePublished." ; + sh:targetNode ro: ; + sh:severity sh:Warning ; + sh:message "Published crates SHOULD include schema:datePublished." ; + sh:or ( + # datePublished not required if no publisher: + [ sh:not [ sh:property [ + sh:path schema:publisher ; + sh:minCount 1 ; + ]]] + # datePublished required if publisher present: + [ sh:property [ + sh:path schema:datePublished ; + sh:minCount 1 ; + ]] + ) . -five-safes-crate:RootDatasetLicenceWhenPublished - a sh:NodeShape ; - sh:name "licence on published crates" ; - sh:description "If the root dataset is published (has schema:publisher), it SHOULD declare a licence." ; - sh:targetNode ro: ; - sh:severity sh:Warning ; - sh:message "Profile Conformance: Published crates SHOULD include a licence (schema:license or schema:licence)." ; - sh:or ( - # license not required if no publisher: - [ sh:not [ sh:property [ - sh:path schema:publisher ; - sh:minCount 1 ; - ]]] - # license/licence required if publisher present: - [ sh:property [ - sh:path [ sh:alternativePath ( schema:license schema:licence ) ] ; - sh:minCount 1 ; - ]] - ) . \ No newline at end of file +five-safes-crate:RootDatasetLicenseWhenPublished + a sh:NodeShape ; + sh:name "License present on published crates" ; + sh:description "If the root dataset is published (has schema:publisher), it SHOULD declare a license." ; + sh:targetNode ro: ; + sh:severity sh:Warning ; + sh:message "Profile Conformance: Published crates SHOULD include a license." ; + sh:or ( + # license not required if no publisher: + [ sh:not [ sh:property [ + sh:path schema:publisher ; + sh:minCount 1 ; + ]]] + # license required if publisher present: + [ sh:property [ + sh:path schema:license ; + sh:minCount 1 ; + ]] + ) . \ No newline at end of file