diff --git a/rocrate_validator/profiles/five-safes-crate/may/11_workflow_execution_phase.ttl b/rocrate_validator/profiles/five-safes-crate/may/11_workflow_execution_phase.ttl
new file mode 100644
index 000000000..4a043d096
--- /dev/null
+++ b/rocrate_validator/profiles/five-safes-crate/may/11_workflow_execution_phase.ttl
@@ -0,0 +1,58 @@
+# 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:WorkflowexecutionObjectHasStartTimeIfBegun
+ a sh:NodeShape ;
+ sh:name "WorkflowExecution" ;
+ sh:description "The workflow execution object MAY have a startTime if execution was initiated." ;
+
+ sh:target [
+ a sh:SPARQLTarget ;
+ sh:select """
+ PREFIX schema:
+ PREFIX rdf:
+
+ SELECT ?this
+ WHERE {
+ ?this rdf:type schema:CreateAction ;
+ schema:actionStatus ?status .
+ FILTER(?status IN (
+ "http://schema.org/CompletedActionStatus",
+ "http://schema.org/FailedActionStatus",
+ "http://schema.org/ActiveActionStatus"
+ ))
+ }
+ """ ;
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "StartTime" ;
+ sh:path schema:startTime ;
+ sh:minCount 1 ;
+ sh:maxCount 1 ;
+ sh:severity sh:Info ;
+ sh:description "The workflow execution object MAY have a startTime if execution was initiated." ;
+ sh:message "The workflow execution object MAY have a startTime if execution was initiated." ;
+ ] .
diff --git a/rocrate_validator/profiles/five-safes-crate/must/11_workflow_execution_phase.ttl b/rocrate_validator/profiles/five-safes-crate/must/11_workflow_execution_phase.ttl
new file mode 100644
index 000000000..a132fbe10
--- /dev/null
+++ b/rocrate_validator/profiles/five-safes-crate/must/11_workflow_execution_phase.ttl
@@ -0,0 +1,96 @@
+# 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 purl: .
+@prefix sh: .
+@prefix validator: .
+@prefix xsd: .
+
+
+five-safes-crate:WorkflowMustHaveDescriptiveName
+ a sh:NodeShape ;
+ sh:name "WorkflowExecution" ;
+ sh:targetClass schema:CreateAction ;
+
+ sh:property [
+ sh:a sh:PropertyShape ;
+ sh:name "name" ;
+ sh:minCount 1 ;
+ sh:description "Workflow (CreateAction) MUST have a name string of at least 10 characters." ;
+ sh:path schema:name ;
+ sh:datatype xsd:string ;
+ sh:minLength 10 ;
+ sh:severity sh:Violation ;
+ sh:message "Workflow (CreateAction) MUST have a name string of at least 10 characters." ;
+ ] .
+
+
+five-safes-crate:WorkflowexecutionObjectHasCompliantStartTimeFormat
+ a sh:NodeShape ;
+ sh:name "WorkflowExecution" ;
+ sh:description "The startTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ;
+ sh:targetClass schema:CreateAction ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "StartTime" ;
+ sh:path schema:startTime ;
+ sh:minCount 0 ;
+ sh:pattern "^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|z|[+-][0-9]{2}:[0-9]{2})$" ;
+ sh:severity sh:Violation ;
+ sh:message "The startTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ;
+ ] .
+
+
+five-safes-crate:WorkflowexecutionObjectHasCompliantEndTimeFormat
+ a sh:NodeShape ;
+ sh:name "WorkflowExecution" ;
+ sh:description "The endTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ;
+ sh:targetClass schema:CreateAction ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "EndTime" ;
+ sh:path schema:endTime ;
+ sh:minCount 0 ;
+ sh:pattern "^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|z|[+-][0-9]{2}:[0-9]{2})$" ;
+ sh:severity sh:Violation ;
+ sh:message "The endTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ;
+ ] .
+
+
+five-safes-crate:WorkflowMustHaveActionStatusWithAllowedValues
+ a sh:NodeShape ;
+ sh:name "WorkflowExecution" ;
+ sh:targetClass schema:CreateAction ;
+ sh:property [
+ a sh:PropertyShape ;
+ sh:minCount 1 ;
+ sh:name "actionStatus" ;
+ sh:description "WorkflowExecution MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ;
+ sh:path schema:actionStatus ;
+ sh:in (
+ "http://schema.org/PotentialActionStatus"
+ "http://schema.org/ActiveActionStatus"
+ "http://schema.org/CompletedActionStatus"
+ "http://schema.org/FailedActionStatus"
+ ) ;
+ sh:severity sh:Violation ;
+ sh:message "WorkflowExecution MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ;
+ ] .
\ No newline at end of file
diff --git a/rocrate_validator/profiles/five-safes-crate/should/11_workflow_execution_phase.ttl b/rocrate_validator/profiles/five-safes-crate/should/11_workflow_execution_phase.ttl
new file mode 100644
index 000000000..0c1523102
--- /dev/null
+++ b/rocrate_validator/profiles/five-safes-crate/should/11_workflow_execution_phase.ttl
@@ -0,0 +1,85 @@
+# 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 purl: .
+@prefix sh: .
+@prefix validator: .
+@prefix xsd: .
+
+
+
+five-safes-crate:RootDataEntityShouldMentionWorkflow
+ a sh:NodeShape ;
+ sh:name "RootDataEntity" ;
+ sh:description "RootDataEntity SHOULD mention workflow execution object (typed CreateAction)." ;
+ sh:targetClass ro-crate:RootDataEntity ;
+ sh:sparql [
+ a sh:SPARQLConstraint ;
+ sh:name "mentions" ;
+ sh:select """
+ PREFIX schema:
+ PREFIX rdf:
+ SELECT $this
+ WHERE {
+
+ FILTER NOT EXISTS {
+ $this schema:mentions ?workflowExecution .
+ ?workflowExecution rdf:type schema:CreateAction .
+ }
+ }
+ """ ;
+ sh:severity sh:Warning ;
+ sh:message "RootDataEntity SHOULD mention workflow execution object (typed CreateAction)." ;
+ ] .
+
+
+
+five-safes-crate:WorkflowexecutionObjectHasEndTimeIfEnded
+ a sh:NodeShape ;
+ sh:name "WorkflowExecution" ;
+ sh:description "The workflow execution object SHOULD have an endTime property if it has ended." ;
+
+ sh:target [
+ a sh:SPARQLTarget ;
+ sh:select """
+ PREFIX schema:
+ PREFIX rdf:
+
+ SELECT ?this
+ WHERE {
+ ?this rdf:type schema:CreateAction ;
+ schema:actionStatus ?status .
+ FILTER(?status IN (
+ "http://schema.org/CompletedActionStatus",
+ "http://schema.org/FailedActionStatus"
+ ))
+ }
+ """ ;
+ ] ;
+
+ sh:property [
+ a sh:PropertyShape ;
+ sh:name "EndTime" ;
+ sh:path schema:endTime ;
+ sh:minCount 1 ;
+ sh:maxCount 1 ;
+ sh:severity sh:Warning ;
+ sh:description "The workflow execution object SHOULD have an endTime property if it has ended." ;
+ sh:message "The workflow execution object SHOULD have an endTime property if it has ended." ;
+ ] .
\ 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
index f1f48cd99..e4aa7f030 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
@@ -106,7 +106,9 @@
{
"@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538",
"@type": "CreateAction",
- "actionStatus": "http://schema.org/CompleteActionStatus",
+ "startTime": "2023-04-18T12:12:00+01:00",
+ "endTime": "2023-04-19T16:59:59+01:00",
+ "actionStatus": "http://schema.org/CompletedActionStatus",
"agent": {
"@id": "https://orcid.org/0000-0001-9842-9718"
},
@@ -160,7 +162,7 @@
"@type": "PropertyValue",
"name": "tre72",
"value": "project81"
- },
+ },
{
"@id": "input1.txt",
"@type": "File",
@@ -303,6 +305,7 @@
"@id": "https://w3id.org/shp#DisclosureCheck"
},
"name": "Disclosure check of workflow results: approved",
+ "startTime": "2023-04-24T00:00:00+01:00",
"endTime": "2023-04-25T16:00:00+01:00",
"object": {
"@id": "./"
diff --git a/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py b/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py
new file mode 100644
index 000000000..7bbefd94d
--- /dev/null
+++ b/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py
@@ -0,0 +1,288 @@
+# 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_workflow_object_with_no_name():
+
+ sparql = (
+ SPARQL_PREFIXES + """
+ DELETE {
+ ?this schema:name ?name .
+ }
+ WHERE {
+ ?this rdf:type schema:CreateAction ;
+ schema:name ?name .
+ <./> schema:mentions ?this .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=["Workflow (CreateAction) MUST have a name string of at least 10 characters."],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_workflow_object_with_name_not_string():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?this schema:name ?name .
+ }
+ INSERT {
+ ?this schema:name 123 .
+ }
+ WHERE {
+ ?this rdf:type schema:CreateAction ;
+ schema:name ?name .
+ <./> schema:mentions ?this .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=["Workflow (CreateAction) MUST have a name string of at least 10 characters."],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_workflow_object_with_not_long_enough_name():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?this schema:name ?name .
+ }
+ INSERT {
+ ?this schema:name "Short" .
+ }
+ WHERE {
+ ?this rdf:type schema:CreateAction ;
+ schema:name ?name .
+ <./> schema:mentions ?this .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=["Workflow (CreateAction) MUST have a name string of at least 10 characters."],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_workflow_object_has_no_properly_formatted_start_time():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?s schema:startTime ?time .
+ }
+ INSERT {
+ ?s schema:startTime "1st Dec '25 @ 10:00:00" .
+ }
+ WHERE {
+ ?s rdf:type schema:CreateAction ;
+ schema:startTime ?time .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=[(
+ "The startTime of the workflow execution object MUST follow the RFC 3339 standard "
+ "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))."
+ )],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_workflow_object_has_no_properly_formatted_end_time():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?s schema:endTime ?time .
+ }
+ INSERT {
+ ?s schema:endTime "1st Dec '25 @ 10:00:00" .
+ }
+ WHERE {
+ ?s rdf:type schema:CreateAction ;
+ schema:endTime ?time .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=[(
+ "The endTime of the workflow execution object MUST follow the RFC 3339 standard "
+ "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))."
+ )],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_workflow_object_with_no_action_status():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?this schema:actionStatus ?o .
+ }
+ WHERE {
+ ?this schema:actionStatus ?o ;
+ rdf:type schema:CreateAction .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=[(
+ "WorkflowExecution MUST have an actionStatus "
+ "with an allowed value (see https://schema.org/ActionStatusType)."
+ )],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_workflow_object_with_no_properly_valued_action_status():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?this schema:actionStatus ?o .
+ }
+ INSERT {
+ ?this schema:actionStatus "Not a proper actionStatus value" .
+ }
+ WHERE {
+ ?this schema:actionStatus ?o ;
+ rdf:type schema:CreateAction .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.REQUIRED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=[(
+ "WorkflowExecution MUST have an actionStatus "
+ "with an allowed value (see https://schema.org/ActionStatusType)."
+ )],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+# ----- SHOULD fails tests
+
+
+def test_5src_workflow_object_not_mentioned_by_root_data_entity():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ <./> schema:mentions ?o .
+ }
+ WHERE {
+ ?o rdf:type schema:CreateAction .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.RECOMMENDED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["RootDataEntity"],
+ expected_triggered_issues=[
+ "RootDataEntity SHOULD mention workflow execution object (typed CreateAction)."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+def test_5src_workflow_object_has_no_end_time_if_ended():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?s schema:endTime ?time .
+ }
+ WHERE {
+ ?s rdf:type schema:CreateAction ;
+ schema:endTime ?time .
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.RECOMMENDED,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=[
+ "The workflow execution object SHOULD have an endTime property if it has ended."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
+
+
+# ------- MAY fail tests
+
+
+def test_5src_workflow_object_has_no_start_time_if_begun():
+ sparql = (SPARQL_PREFIXES + """
+ DELETE {
+ ?s schema:startTime ?time .
+ }
+ WHERE {
+ ?s rdf:type schema:CreateAction;
+ schema:startTime ?time
+ }
+ """)
+
+ do_entity_test(
+ rocrate_path=ValidROC().five_safes_crate_result,
+ requirement_severity=Severity.OPTIONAL,
+ expected_validation_result=False,
+ expected_triggered_requirements=["WorkflowExecution"],
+ expected_triggered_issues=[
+ "The workflow execution object MAY have a startTime if execution was initiated."
+ ],
+ profile_identifier="five-safes-crate",
+ rocrate_entity_mod_sparql=sparql,
+ )
diff --git a/tests/shared.py b/tests/shared.py
index 978f60e4a..b82d4ade7 100644
--- a/tests/shared.py
+++ b/tests/shared.py
@@ -34,7 +34,10 @@
T = TypeVar("T")
-SPARQL_PREFIXES = """PREFIX schema:
+SPARQL_PREFIXES = """
+PREFIX schema:
+PREFIX shp:
+PREFIX rdf:
"""