diff --git a/rocrate_validator/profiles/five-safes-crate/may/1_responsible_project.ttl b/rocrate_validator/profiles/five-safes-crate/may/1_responsible_project.ttl
new file mode 100644
index 000000000..5dd46fd27
--- /dev/null
+++ b/rocrate_validator/profiles/five-safes-crate/may/1_responsible_project.ttl
@@ -0,0 +1,56 @@
+# 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: .
+
+
+five-safes-crate:ResponsibleProject
+ a sh:NodeShape ;
+ sh:name "Responsible Project" ;
+ sh:target [
+ a sh:SPARQLTarget ;
+ sh:prefixes ro-crate:sparqlPrefixes ;
+ sh:select """
+ SELECT DISTINCT ?this WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?this .
+ }
+ """
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "funding" ;
+ sh:path schema:funding;
+ sh:minCount 1 ;
+ sh:severity sh:Info ;
+ sh:message """The Responsible Project does not have the property `funding`.""" ;
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "member" ;
+ sh:path schema:member;
+ sh:minCount 1 ;
+ sh:severity sh:Info ;
+ sh:message """The Responsible Project does not have the property `member`.""" ;
+ ] .
\ No newline at end of file
diff --git a/rocrate_validator/profiles/five-safes-crate/must/1_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/must/1_requesting_agent.ttl
new file mode 100644
index 000000000..2cc0d434d
--- /dev/null
+++ b/rocrate_validator/profiles/five-safes-crate/must/1_requesting_agent.ttl
@@ -0,0 +1,82 @@
+# 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: .
+
+
+five-safes-crate:AgentIsMemberOf
+ a sh:NodeShape ;
+ sh:name "Requesting Agent" ;
+ sh:target [
+ a sh:SPARQLTarget ;
+ sh:prefixes ro-crate:sparqlPrefixes ;
+ sh:select """
+ SELECT DISTINCT ?this WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?this .
+ }
+ """
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "memberOf" ;
+ sh:path schema:memberOf;
+ sh:class schema:Project ;
+ sh:severity sh:Violation ;
+ sh:message """The 'memberOf' property of an agent MUST be of type Project.""" ;
+ ] .
+
+
+five-safes-crate:AgentProjectIntersection
+ a sh:NodeShape ;
+ sh:name "Agent Project Intersection" ;
+ sh:description """At least one Project referenced by Agent -> memberOf MUST be included in the set of Projects referenced by RootDataEntity -> sourceOrganization.""" ;
+ sh:target [
+ a sh:SPARQLTarget ;
+ sh:prefixes ro-crate:sparqlPrefixes ;
+ sh:select """
+ SELECT DISTINCT ?this WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?this .
+ }
+ """
+ ] ;
+ sh:sparql [
+ a sh:SPARQLConstraint ;
+ sh:name "Agent Project Intersection" ;
+ sh:description """At least one Project referenced by Agent -> memberOf MUST be included in the set of Projects referenced by RootDataEntity -> sourceOrganization.""" ;
+ sh:prefixes ro-crate:sparqlPrefixes ;
+ sh:select """
+ SELECT $this WHERE {
+ FILTER EXISTS {
+ $this schema:memberOf ?anyProject .
+ }
+ FILTER NOT EXISTS {
+ $this schema:memberOf ?commonProject .
+ ?metadata schema:about ?root .
+ ?root schema:sourceOrganization ?commonProject .
+ }
+ }
+ """ ;
+ sh:severity sh:Violation ;
+ sh:message """At least one Project referenced by Agent -> memberOf MUST be included in the set of Projects referenced by RootDataEntity -> sourceOrganization.""" ;
+ ] .
\ No newline at end of file
diff --git a/rocrate_validator/profiles/five-safes-crate/must/1_responsible_project.ttl b/rocrate_validator/profiles/five-safes-crate/must/1_responsible_project.ttl
new file mode 100644
index 000000000..c06c873ea
--- /dev/null
+++ b/rocrate_validator/profiles/five-safes-crate/must/1_responsible_project.ttl
@@ -0,0 +1,59 @@
+# 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: .
+
+
+five-safes-crate:ResponsibleProject
+ a sh:NodeShape ;
+ sh:name "Responsible Project" ;
+ sh:target [
+ a sh:SPARQLTarget ;
+ sh:prefixes ro-crate:sparqlPrefixes ;
+ sh:select """
+ SELECT DISTINCT ?this WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?this .
+ }
+ """
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "funding" ;
+ sh:path schema:funding;
+ sh:class schema:Grant ;
+ sh:severity sh:Violation ;
+ sh:message """The property 'funding' of the Responsible Project MUST be of type schema:Grant.""" ;
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "member" ;
+ sh:path schema:member;
+ sh:or (
+ [ sh:class schema:Organization ]
+ [ sh:class schema:Person ]
+ ) ;
+ sh:severity sh:Violation ;
+ sh:message """The property 'member' of the Responsible Project MUST be of type schema:Organization or schema:Person.""" ;
+ ] .
\ No newline at end of file
diff --git a/rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity_metadata.ttl b/rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity.ttl
similarity index 65%
rename from rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity_metadata.ttl
rename to rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity.ttl
index 1145bb5a3..bb9a51471 100644
--- a/rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity_metadata.ttl
+++ b/rocrate_validator/profiles/five-safes-crate/must/1_root_data_entity.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.
@@ -16,23 +16,31 @@
@prefix ro-crate: .
@prefix five-safes-crate: .
@prefix rdf: .
-@prefix schema_org: .
+@prefix schema: .
@prefix sh: .
@prefix validator: .
@prefix xsd: .
+
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 `sourceOrganisation`" ;
+ sh:name "RootDataEntity" ;
sh:targetClass ro-crate:RootDataEntity ;
+
sh:property [
a sh:PropertyShape ;
- 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:name "sourceOrganization" ;
+ sh:path schema:sourceOrganization;
sh:minCount 1 ;
- sh:nodeKind sh:IRI;
- 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.""" ;
+ sh:severity sh:Violation ;
+ sh:message """The Root Data Entity MUST have a `sourceOrganization` property.""" ;
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "sourceOrganization" ;
+ sh:path schema:sourceOrganization ;
+ sh:class schema:Project ;
+ sh:severity sh:Violation ;
+ sh:message """The `sourceOrganization` property of the RootDataEntity MUST point to a Project entity.""" ;
] .
diff --git a/rocrate_validator/profiles/five-safes-crate/must/1_funder.ttl b/rocrate_validator/profiles/five-safes-crate/should/1_requesting_agent.ttl
similarity index 51%
rename from rocrate_validator/profiles/five-safes-crate/must/1_funder.ttl
rename to rocrate_validator/profiles/five-safes-crate/should/1_requesting_agent.ttl
index 474fda436..332c67d3d 100644
--- a/rocrate_validator/profiles/five-safes-crate/must/1_funder.ttl
+++ b/rocrate_validator/profiles/five-safes-crate/should/1_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.
@@ -15,22 +15,32 @@
@prefix ro: <./> .
@prefix ro-crate: .
@prefix five-safes-crate: .
-@prefix schema_org: .
+@prefix rdf: .
+@prefix schema: .
@prefix sh: .
-@prefix bioschemas: .
+@prefix validator: .
+@prefix xsd: .
+
+
+five-safes-crate:AgentIsMemberOf
+ a sh:NodeShape ;
+ sh:name "Requesting Agent" ;
+ sh:target [
+ a sh:SPARQLTarget ;
+ sh:prefixes ro-crate:sparqlPrefixes ;
+ sh:select """
+ SELECT DISTINCT ?this WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?this .
+ }
+ """
+ ] ;
-# TODO: note that this applies to all Projects, not just those targeted by `funding` relationships
-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:name "memberOf" ;
+ sh:path schema:memberOf;
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)" ;
- ] .
+ sh:severity sh:Warning ;
+ sh:message """The Requesting Agent SHOULD have a `memberOf` property.""" ;
+ ] .
\ No newline at end of file
diff --git a/rocrate_validator/profiles/five-safes-crate/should/1_responsible_project.ttl b/rocrate_validator/profiles/five-safes-crate/should/1_responsible_project.ttl
new file mode 100644
index 000000000..28d1c1eef
--- /dev/null
+++ b/rocrate_validator/profiles/five-safes-crate/should/1_responsible_project.ttl
@@ -0,0 +1,60 @@
+# 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: .
+
+
+five-safes-crate:ResponsibleProjectMemberAndSourceOrganizationIntersection
+ a sh:NodeShape ;
+ sh:name "Organizations (members of Responsible Project)" ;
+ sh:description """At least one of the organisations that are members of the responsible project SHOULD be included in the Requesting Agent's affiliations, if such properties exist.""" ;
+ 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 ;
+ schema:memberOf ?project ;
+ schema:affiliation ?someAffiliation .
+ ?project schema:member ?org2 .
+ }
+ """
+ ] ;
+ sh:sparql [
+ a sh:SPARQLConstraint ;
+ sh:name "Intersection with agent affiliations" ;
+ sh:description """At least one of the organisations that are members of the responsible project SHOULD be included in the Requesting Agent's affiliations, if such properties exist.""" ;
+
+ sh:prefixes ro-crate:sparqlPrefixes ;
+ sh:select """
+ SELECT $this WHERE {
+ FILTER NOT EXISTS {
+ $this schema:affiliation ?org .
+ $this schema:memberOf ?project .
+ ?project schema:member ?org .
+ }
+ }
+ """ ;
+ sh:severity sh:Warning ;
+ sh:message """At least one of the organisations that are members of the responsible project SHOULD be included in the Requesting Agent's affiliations, if such properties exist.""" ;
+ ] .
\ No newline at end of file
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 9fb0d7bf5..732854019 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
@@ -101,6 +101,11 @@
"@type": "Organization",
"name": "The University of Manchester"
},
+ {
+ "@id": "https://ror.org/01ee9ar58",
+ "@type": "Organization",
+ "name": "University of Nottingham"
+ },
{
"@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70",
"@type": "Project",
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 a6475f63c..f1f48cd99 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
@@ -366,6 +366,11 @@
"@type": "Organization",
"name": "The University of Manchester"
},
+ {
+ "@id": "https://ror.org/01ee9ar58",
+ "@type": "Organization",
+ "name": "University of Nottingham"
+ },
{
"@id": "https://gtr.ukri.org/projects?ref=10038961",
"@type": "Grant",
diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py b/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py
new file mode 100644
index 000000000..9601f6fa3
--- /dev/null
+++ b/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py
@@ -0,0 +1,138 @@
+# 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.ro_crates import ValidROC
+from tests.shared import do_entity_test, SPARQL_PREFIXES
+
+# set up logging
+logger = logging.getLogger(__name__)
+
+
+# ----- MUST fails tests
+
+
+def test_5src_agent_memberOf_not_project():
+ """
+ Test a Five Safes Crate where an agent's `memberOf` does NOT reference a schema:Project.
+ (We replace the referenced Project with a plain literal.)
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?agent schema:memberOf ?org .
+ }
+ INSERT {
+ ?agent schema:memberOf "Not a project (literal replacement)"
+ }
+ WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?org .
+ ?org a schema:Project .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["Requesting Agent"],
+ expected_triggered_issues=[
+ "The 'memberOf' property of an agent MUST be of type Project."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_agent_memberOf_project_not_in_root():
+ """
+ Test a Five Safes Crate where NONE of the Projects referenced by Agent->memberOf are included
+ in the set of Projects referenced by RootDataEntity->sourceOrganization.
+ (We replace an agent's memberOf with a new Project that the root does not reference.)
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?agent schema:memberOf ?org .
+ }
+ INSERT {
+ # assign the agent to a new Project that is not referenced by the Root Data Entity
+ ?agent schema:memberOf <#missing-project> .
+ <#missing-project> a schema:Project .
+ }
+ WHERE {
+ # locate a CreateAction -> agent -> memberOf that currently points to a Project
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+
+ ?agent schema:memberOf ?org .
+ ?org a schema:Project .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["Agent Project Intersection"],
+ expected_triggered_issues=[
+ (
+ "At least one Project referenced by Agent -> memberOf MUST be included "
+ "in the set of Projects referenced by RootDataEntity -> sourceOrganization."
+ )
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+# ----- SHOULD warns tests
+def test_5src_agent_memberOf_missing_warning():
+ """
+ Test a Five Safes Crate where the Requesting Agent does NOT have the 'memberOf' property.
+ This should trigger the SHACL warning: the Requesting Agent SHOULD have a `memberOf` property.
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?agent schema:memberOf ?org .
+ }
+ WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?org .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.RECOMMENDED,
+ expected_validation_result=False, # or True if warnings are not treated as failures
+ expected_triggered_requirements=["Requesting Agent"],
+ expected_triggered_issues=[
+ "The Requesting Agent SHOULD have a `memberOf` property."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py b/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py
new file mode 100644
index 000000000..daa8d90ac
--- /dev/null
+++ b/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py
@@ -0,0 +1,218 @@
+# 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.ro_crates import ValidROC
+from tests.shared import do_entity_test, SPARQL_PREFIXES
+
+# set up logging
+logger = logging.getLogger(__name__)
+
+
+# ---- MUST fails tests
+
+
+def test_5src_responsible_project_funding_not_grant():
+ """
+ Test a Five Safes Crate where a Responsible Project's `funding` property
+ is NOT of type schema:Grant.
+ (We replace the funding reference with a literal.)
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?project schema:funding ?grant .
+ }
+ INSERT {
+ ?project schema:funding "Not a grant (literal replacement)" .
+ }
+ WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?project .
+ ?project schema:funding ?grant .
+ ?grant a schema:Grant .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["Responsible Project"],
+ expected_triggered_issues=[
+ "The property 'funding' of the Responsible Project MUST be of type schema:Grant."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_responsible_project_member_not_organization():
+ """
+ Test a Five Safes Crate where a Responsible Project's `member` property
+ is NOT of type schema:Organization.
+ (We replace the member reference with a literal.)
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?project schema:member ?org .
+ }
+ INSERT {
+ ?project schema:member "Not organization or person (literal replacement)" .
+ }
+ WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?project .
+ ?project schema:member ?org .
+ ?org a schema:Organization .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["Responsible Project"],
+ expected_triggered_issues=[
+ "The property 'member' of the Responsible Project MUST be of type schema:Organization or schema:Person."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+# ---- SHOULD warns tests
+
+
+def test_5src_responsible_project_member_and_agent_affiliation_no_intersection():
+ """
+ Test a Five Safes Crate where none of the organisations that are members of the
+ Responsible Project appear in the Requesting Agent's affiliations (violates the
+ 'Intersection with agent affiliations' SHACL warning).
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?agent schema:affiliation ?oldAff .
+ }
+ INSERT {
+ ?agent schema:affiliation <#missing-affiliation> .
+ <#missing-affiliation> a schema:Organization .
+ }
+ WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent a schema:Person ;
+ schema:memberOf ?project ;
+ schema:affiliation ?oldAff .
+ ?project schema:member ?oldAff .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.RECOMMENDED,
+ expected_validation_result=False,
+ expected_triggered_requirements=[
+ "Organizations (members of Responsible Project)"
+ ],
+ expected_triggered_issues=[
+ (
+ "At least one of the organisations that are members of the responsible project SHOULD "
+ "be included in the Requesting Agent's affiliations, if such properties exist."
+ )
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+# ---- MAY warns tests
+
+
+def test_5src_responsible_project_missing_funding_property():
+ """
+ Test a Five Safes Crate where a Responsible Project does NOT have the `funding` property.
+ This should trigger the SHACL info: 'The Responsible Project does not have the property `funding`.'
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?project schema:funding ?f .
+ }
+ WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?project .
+ ?project schema:funding ?f .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.OPTIONAL,
+ expected_validation_result=False,
+ expected_triggered_requirements=["Responsible Project"],
+ expected_triggered_issues=[
+ "The Responsible Project does not have the property `funding`."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_responsible_project_missing_member_property():
+ """
+ Test a Five Safes Crate where a Responsible Project does NOT have the `member` property.
+ This should trigger the SHACL info: 'The Responsible Project does not have the property `member`.'
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ ?project schema:member ?m .
+ }
+ WHERE {
+ ?action a schema:CreateAction ;
+ schema:agent ?agent .
+ ?agent schema:memberOf ?project .
+ ?project schema:member ?m .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.OPTIONAL,
+ expected_validation_result=False,
+ expected_triggered_requirements=["Responsible Project"],
+ expected_triggered_issues=[
+ "The Responsible Project does not have the property `member`."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py b/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py
new file mode 100644
index 000000000..54ddce7ee
--- /dev/null
+++ b/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py
@@ -0,0 +1,89 @@
+# 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.ro_crates import ValidROC
+from tests.shared import do_entity_test, SPARQL_PREFIXES
+
+# set up logging
+logger = logging.getLogger(__name__)
+
+
+# ----- MUST fails tests
+
+
+def test_5src_root_data_entity_without_source_organization():
+ """
+ Test a Five Safes Crate where the Root Data Entity does NOT have the 'sourceOrganization' property.
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ <./> schema:sourceOrganization ?o .
+ }
+ WHERE {
+ <./> schema:sourceOrganization ?o .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["RootDataEntity"],
+ expected_triggered_issues=[
+ """The Root Data Entity MUST have a `sourceOrganization` property."""
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_root_data_entity_source_organization_not_organization():
+ """
+ Test a Five Safes Crate where the Root Data Entity's `sourceOrganization` property
+ does NOT reference a `schema:Project` entity.
+ (We replace any existing sourceOrganization with a literal to violate the class constraint.)
+ """
+ sparql = (
+ SPARQL_PREFIXES
+ + """
+ DELETE {
+ <./> schema:sourceOrganization ?o .
+ }
+ INSERT {
+ # insert a literal instead of an IRI
+ <./> schema:sourceOrganization "Investigation of cancer (TRE72 project 81)" .
+ }
+ WHERE {
+ <./> schema:sourceOrganization ?o .
+ }
+ """
+ )
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_request,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["RootDataEntity"],
+ expected_triggered_issues=[
+ """The `sourceOrganization` property of the RootDataEntity MUST point to a Project entity."""
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
diff --git a/tests/integration/profiles/five-safes-crate/test_5src_funding.py b/tests/integration/profiles/five-safes-crate/test_5src_funding.py
deleted file mode 100644
index 58b2f5746..000000000
--- a/tests/integration/profiles/five-safes-crate/test_5src_funding.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# 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.ro_crates import ValidROC
-from tests.shared import do_entity_test, SPARQL_PREFIXES
-
-# set up logging
-logger = logging.getLogger(__name__)
-
-
-def test_5src_funding_project_no_name():
- """\
- Test a Five Safes Crate where the funding Project does not have a name.
- """
- sparql = SPARQL_PREFIXES + """DELETE WHERE {
- <#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70> schema:name "Investigation of cancer (TRE72 project 81)"
-}
-"""
-
- do_entity_test(
- rocrate_path=ValidROC().five_safes_crate_result,
- requirement_severity=Severity.REQUIRED,
- expected_validation_result=False,
- expected_triggered_requirements=["Funding body Project"],
- expected_triggered_issues=[
- "The Project Entity MUST have a `name` property (as specified by schema.org)"
- ],
- profile_identifier="five-safes-crate",
- rocrate_entity_mod_sparql=sparql,
- )
diff --git a/tests/integration/profiles/five-safes-crate/test_5src_root_data_entity_metadata.py b/tests/integration/profiles/five-safes-crate/test_5src_root_data_entity_metadata.py
deleted file mode 100644
index 92417caa7..000000000
--- a/tests/integration/profiles/five-safes-crate/test_5src_root_data_entity_metadata.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# 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.ro_crates import ValidROC
-from tests.shared import do_entity_test, SPARQL_PREFIXES
-
-# set up logging
-logger = logging.getLogger(__name__)
-
-
-def test_5src_root_data_entity_no_source_organization():
- """\
- Test a Five Safes Crate where the Root Data Entity it does not reference a sourceOrganization.
- """
- sparql = SPARQL_PREFIXES + """DELETE WHERE {
- <./> schema:sourceOrganization ?object
- }
- """
-
- do_entity_test(
- rocrate_path=ValidROC().five_safes_crate_result,
- requirement_severity=Severity.REQUIRED,
- expected_validation_result=False,
- expected_triggered_requirements=["Five Safes Crate Root Data Entity REQUIRED properties"],
- expected_triggered_issues=[
- """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."""
- ],
- profile_identifier="five-safes-crate",
- rocrate_entity_mod_sparql=sparql,
- )
-
-
-def test_5src_root_data_entity_source_organization_not_entity():
- """\
- Test a Five Safes Crate where the Root Data Entity it does not reference a sourceOrganization.
- """
- sparql = SPARQL_PREFIXES + """DELETE {
- <./> schema:sourceOrganization ?o
- }
- INSERT {
- <./> schema:sourceOrganization "Investigation of cancer (TRE72 project 81)"
- }
- WHERE {
- <./> schema:sourceOrganization ?o
- }
-
-"""
-
- do_entity_test(
- rocrate_path=ValidROC().five_safes_crate_result,
- requirement_severity=Severity.REQUIRED,
- expected_validation_result=False,
- expected_triggered_requirements=["Five Safes Crate Root Data Entity REQUIRED properties"],
- expected_triggered_issues=[
- """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."""
- ],
- profile_identifier="five-safes-crate",
- rocrate_entity_mod_sparql=sparql,
- )